Compare commits

...

786 Commits

Author SHA1 Message Date
8669057681 remove debug line 2019-07-06 21:44:31 +08:00
d0fe2d2f75 fix issue in gdb.Model.Scan 2019-07-06 15:51:32 +08:00
49ef4fd266 add more example for gconv.Map 2019-07-06 15:02:02 +08:00
929a57ceb8 add Structs/StructsDeep function for gconv; add auto-creating struct/struct pointer for gconv.Struct function when parameter pointer is typeof **struct 2019-07-06 11:10:32 +08:00
4670a5d2e2 TODO++; version updates 2019-07-04 19:22:11 +08:00
273d992493 fix issue in gfsnotify in recursive inotify watcher registering 2019-07-04 19:06:06 +08:00
ed61c2ee22 improve gconv.Map for priority tags 2019-07-04 14:27:43 +08:00
b29c6add47 rename internal/structtag to internal/structs; add more feature for internal/structs; improve gvalid to support recursive validation for struct; improve gconv.Struct/Map functions 2019-07-04 11:11:41 +08:00
8d01e565c5 gfsnotify example updates 2019-07-03 22:27:50 +08:00
47e0fb95d5 add package internal/structtag; improve ghttp.Request.Get*ToStruct functions to support substruct with 'param' tag; improve gtest.compareMap; improve gconv.Map using internal/structtag 2019-07-03 22:09:35 +08:00
23d346b291 improve gdb.Model.Struct/Structs/Scan in error handling, it returns sql.ErrorNoRows if no records received after querying 2019-07-03 19:38:52 +08:00
c90ed0d424 comment updates for gfsnotify 2019-07-02 23:57:49 +08:00
418cbb420b TODO updates; version updates 2019-07-02 23:48:38 +08:00
c948f0c287 improve gdb.Update/Delete feature to support orderby/limit features 2019-07-02 23:14:40 +08:00
e63e989d41 fix issue of dead lock in gcache.doSetWithLockCheck 2019-07-02 19:22:06 +08:00
31921905a9 update unit test cases for gaes because of changes of gbase64 2019-07-02 17:40:16 +08:00
5572ab858e improve gbase64 2019-07-02 17:30:18 +08:00
961ca0879d improve gbase64 2019-07-02 16:56:10 +08:00
0ff31012c8 donator updates 2019-06-30 19:31:18 +08:00
aa6110c619 Merge branch 'master' of https://github.com/gogf/gf 2019-06-27 14:33:57 +08:00
a9660fe9fa add valid tag for gvalid 2019-06-27 14:33:39 +08:00
8e6640ed79 Merge pull request #199 from pibigstar/master 2019-06-26 22:50:43 +08:00
2b0da75412 improve ghttp.Client 2019-06-26 13:55:23 +08:00
da907f35bd improve group router for ghttp 2019-06-25 23:26:47 +08:00
133531b6a0 README updates 2019-06-25 23:17:14 +08:00
5fa0f8dd7e up 2019-06-25 23:06:12 +08:00
7247413a48 Merge branch 'master' of https://github.com/gogf/gf 2019-06-25 23:03:39 +08:00
591158adc5 improve router registry for ghttp 2019-06-25 23:03:29 +08:00
5d83383105 Merge pull request #212 from hailaz/master 2019-06-25 23:02:06 +08:00
5ad809f49e vet the go format 2019-06-25 19:26:02 +08:00
56a62bb4c2 gofmt 2019-06-25 19:01:27 +08:00
afb4a233be Merge pull request #215 from 2892931976/gf-chj 2019-06-25 19:00:02 +08:00
c0aebe023c remove example codes for gcanner 2019-06-25 18:40:55 +08:00
c066fff971 仓库位置更改 2019-06-25 16:03:14 +08:00
89373ebcd6 增加代码风格检查 2019-06-25 16:03:14 +08:00
51cf232691 gutil test 20190615 15:47 2019-06-25 15:50:06 +08:00
f25571a0a9 Improve gmlock, gmutex, grpool, gmutex unit testing sleep time. 2019-06-24 19:34:53 +08:00
05c7ba5f29 improve ghttp.CORSDefault 2019-06-24 19:05:07 +08:00
dcf7772589 Improve gmlock, gmutex unit testing sleep time. 2019-06-24 18:04:35 +08:00
3806f9db07 Improve gflock unit testing. 2019-06-24 17:33:56 +08:00
0f14002b05 Improve gflock unit testing. 2019-06-24 17:17:24 +08:00
1bf53d8b89 Improve gflock unit testing. 2019-06-24 17:15:46 +08:00
8eb10a58ad Improve gflock. 2019-06-24 17:15:30 +08:00
9985378062 rename gmutex unit test file. 2019-06-24 15:04:33 +08:00
38ad5d457b Improve grpool unit testing. 2019-06-24 15:01:12 +08:00
ebed433dde Merge pull request #209 from guoyahao/master 2019-06-24 13:41:18 +08:00
2b02f7e210 Improve gmutex unit testing. 2019-06-24 12:50:00 +08:00
1434800982 Improve gmlock unit testing. 2019-06-24 09:27:31 +08:00
327e33b827 gutil test 20190623 20:41 2019-06-23 20:44:03 +08:00
d2a053c1d7 no message 2019-06-23 17:53:10 +08:00
abe14e049a gutil test 20190623 16:42 2019-06-23 16:45:30 +08:00
4b9db0c794 fix issue for gmd5/gcrc32 2019-06-22 23:06:44 +08:00
b118cd8f27 README updates 2019-06-22 22:06:39 +08:00
83669964f5 README updates 2019-06-22 22:06:13 +08:00
9248fd6b28 add internal/errors 2019-06-22 22:05:39 +08:00
bb935967ac add MarshalJSON function for gjson/gparser to implements the interface MarshalJSON for json.Marshal 2019-06-22 17:02:36 +08:00
b7e0f1f983 improve grpool 2019-06-22 15:05:15 +08:00
6ee6c007c5 update gmutex and its benchmark test cases 2019-06-22 14:42:27 +08:00
9a507b54d7 add Remove/Clear functions for gmlock 2019-06-22 11:45:58 +08:00
c88f516759 remove mutex usage from gflock 2019-06-22 11:29:51 +08:00
912b743a1e improve Range/SubSlice functions for garray 2019-06-22 11:03:50 +08:00
d546424f18 improve gmutex 2019-06-21 22:37:07 +08:00
fe179fff42 gofmt 2019-06-21 22:23:07 +08:00
dcf7138694 README updates 2019-06-19 09:09:13 +08:00
97df154b78 gofmt 2019-06-19 09:06:52 +08:00
674590e247 improve ghttp.RouterGroup 2019-06-18 22:04:17 +08:00
ab48800401 improve gaes 2019-06-18 20:58:47 +08:00
ea456c5faa DONATOR updates 2019-06-18 20:52:24 +08:00
83d1442781 change strings.ReplaceAll to strings.Replace to adapt go1.11 2019-06-18 19:44:28 +08:00
5888b0e06a fix issue in gmlock 2019-06-18 19:19:43 +08:00
a7dcc2c9c6 improve gmlock 2019-06-18 17:39:28 +08:00
17898cc747 improve gmlock 2019-06-18 17:31:48 +08:00
048c3d5025 Merge pull request #195 from zseeker/master
gaes encryption and decryption add CFB mode
2019-06-18 11:44:54 +08:00
0adcdc6b4a Merge pull request #192 from jroam/master 2019-06-18 11:42:44 +08:00
19d7ad734b Update gqueue_unit_test.go 2019-06-18 11:20:10 +08:00
78ccbeac3b add Cas function for gtype; improve gmlock 2019-06-18 08:37:21 +08:00
2a0eae8975 gaes encryption and decryption add CFB mode 2019-06-18 04:07:35 +08:00
28d25e7812 Update gqueue_unit_test.go 2019-06-17 23:16:22 +08:00
fe795d49f3 fix gfile test of a bug
fix  gfile test of bug
2019-06-17 22:46:36 +08:00
9fad898ee3 Update gqueue_unit_test.go 2019-06-17 22:03:41 +08:00
9a42142ce7 Merge branch 'master' of https://github.com/gogf/gf into gogf-master2 2019-06-17 21:51:38 +08:00
9db8ed2dfc README updates 2019-06-17 20:34:45 +08:00
619a539f2e update unit test cases for gfpool 2019-06-17 20:21:41 +08:00
ae8eb4a1f1 Merge branch 'master' of https://github.com/gogf/gf into develop 2019-06-17 20:20:25 +08:00
9a469e64da Merge pull request #187 from goflyfox/master 2019-06-17 20:19:58 +08:00
f7d1613d62 improve gqueue 2019-06-17 19:54:00 +08:00
80655bce50 add gfpool test:delete error 2019-06-17 18:39:27 +08:00
b3b0ba775c update gfpool test:error 2019-06-17 18:09:56 +08:00
4713739d1a update gfpool test:delete err 2019-06-17 17:52:10 +08:00
53b5de330e Update gqueue_unit_test.go 2019-06-17 11:40:40 +08:00
fd4843c3a1 Update gqueue_unit_test.go 2019-06-17 11:31:03 +08:00
97d2b9e1f9 Update gqueue_unit_test.go 2019-06-17 11:04:56 +08:00
0ab60cb770 Update gqueue_unit_test.go 2019-06-16 23:26:49 +08:00
5b71e92776 Update gqueue_unit_test.go 2019-06-16 23:24:03 +08:00
211678c7d3 Merge branch 'master' into 测试gqueue 2019-06-16 23:16:18 +08:00
d25793ed03 Merge pull request #30 from gogf/master
fix issue in gqueue.Close
2019-06-16 23:14:02 +08:00
98cae011a5 fix issue in gqueue.Close 2019-06-16 23:10:50 +08:00
59ae6f9c10 Merge pull request #29 from gogf/master
日常更新
2019-06-16 23:06:59 +08:00
474d1669d9 fix issue in gqueue.Close 2019-06-16 23:00:47 +08:00
563a1408ea fix issue in gqueue.Close 2019-06-16 23:00:00 +08:00
168c08a6f6 Create gqueue_unit_test.go 2019-06-16 22:55:07 +08:00
82783072ef Merge pull request #28 from gogf/master
fix issue in gqueue.Close
2019-06-16 22:22:12 +08:00
11972ef96c fix issue in gqueue.Close 2019-06-16 22:19:59 +08:00
468ba21283 Merge pull request #27 from gogf/master
日常更新
2019-06-16 22:06:39 +08:00
78b0cae892 先取消本地测试 2019-06-16 22:05:15 +08:00
866482a8e8 fix issue in gqueue.Close 2019-06-16 21:52:41 +08:00
c342310389 add example for garray/gchan/glist 2019-06-16 20:38:05 +08:00
0eb028f0b5 README updates 2019-06-16 20:25:08 +08:00
4ffe4f2262 add returned error for ghttp.ClientResponse.Close 2019-06-16 18:54:48 +08:00
677549ec15 add some gqueue tests 2019-06-16 16:41:12 +08:00
68d8e25bc4 update gfpool test2 2019-06-16 00:07:06 +08:00
4f007fdd44 update gfpool test 2019-06-16 00:04:46 +08:00
54392941f3 add gfpool normal test 2019-06-15 23:45:58 +08:00
ebdad47f2d Merge pull request #181 from goflyfox/master 2019-06-15 23:00:47 +08:00
cb4e36f591 update rwmutex test2 2019-06-15 22:19:37 +08:00
cd30efaaa1 update gcmd test 2019-06-15 22:11:08 +08:00
7c234e0437 golint for gvar 2019-06-15 22:06:47 +08:00
d973c5d5c7 improve communication feature for gproc 2019-06-15 21:41:20 +08:00
a7f15a4e00 change gqueue unit tests 2019-06-15 21:40:36 +08:00
6b5484bf55 Merge branch 'master' into develop 2019-06-15 20:42:48 +08:00
ab38b709b2 add gqueue tests 2019-06-15 18:44:22 +08:00
4118038198 Merge branch 'master' into 测试gqueue 2019-06-15 18:36:54 +08:00
142154c0df add gqueue unit tests 2019-06-15 18:35:36 +08:00
07ae90d64d Merge pull request #26 from gogf/master
日常更新
2019-06-15 18:34:50 +08:00
8a69fd09fc Merge pull request #185 from hailaz/master 2019-06-15 18:34:34 +08:00
c02f502bd8 fix issue in gqueue.Size 2019-06-15 18:30:09 +08:00
d5d6b8c303 comment unused gbtree.BTree.isFull function 2019-06-15 18:03:07 +08:00
5c592afebe Improve gtree unit testing. 2019-06-15 17:51:48 +08:00
8402a2b710 Merge pull request #3 from gogf/master
同步主库
2019-06-15 16:57:46 +08:00
fe152dfa63 comment updates for gtree; fix issue in gtree.AVLTree.Remove 2019-06-15 16:53:36 +08:00
e695983d4d improving gproc 2019-06-15 16:07:36 +08:00
a5ab2ba332 gutil test 2019 06 15 14:20 2019-06-15 14:23:28 +08:00
c6e5a52104 add test file 2019-06-14 23:47:00 +08:00
1b4a879eda Merge pull request #182 from jroam/master 2019-06-14 23:09:33 +08:00
c5aa493d24 README updates 2019-06-14 20:42:15 +08:00
a88363e34c Merge branch '测试glist' 2019-06-14 14:58:33 +08:00
0f1261d0e3 完成glist测试率 2019-06-14 14:58:00 +08:00
c41d11df9f Merge pull request #25 from gogf/master
fix更新
2019-06-14 10:50:05 +08:00
1e680c7a8b fix issue in gcache 2019-06-14 10:44:56 +08:00
e981143ead fix issue in gspath 2019-06-14 10:35:12 +08:00
3e2d5e0bdb update mutex test2 2019-06-14 10:28:24 +08:00
1f19ed71aa update mutex test 2019-06-14 10:20:15 +08:00
4de6881c89 Merge pull request #24 from gogf/master
日常更新
2019-06-14 10:00:18 +08:00
5baa82da8f add gcmd test 2019-06-14 00:35:18 +08:00
48f610a216 add mutex and rwmutex test 2019-06-14 00:03:13 +08:00
e4e58791a6 add empty test 2019-06-13 23:50:12 +08:00
46c42ec249 add unit test cases for cmdenv/gcmd 2019-06-13 22:58:58 +08:00
d068c1418e Merge branch 'master' into develop 2019-06-13 22:40:20 +08:00
41db3a32f4 Merge pull request #180 from jroam/master 2019-06-13 22:37:19 +08:00
b3a00becf3 sort_int恢复成主库代码 2019-06-13 22:18:45 +08:00
d1d8cd8482 用gofmt格式化代码 2019-06-13 21:44:14 +08:00
1228907d59 comment updates for internal 2019-06-13 21:14:46 +08:00
57b54414d6 merge master 2019-06-13 21:00:14 +08:00
548a0c47af README updates 2019-06-13 20:52:36 +08:00
4934564b7b Merge pull request #179 from hailaz/master 2019-06-13 20:32:07 +08:00
7348d14fef shut error printing in unit test cases for gcfg/gview 2019-06-13 20:29:40 +08:00
9cddb7ed9a improve unit test cases for gparser 2019-06-13 20:21:18 +08:00
8c84de3f73 improve unit test cases for gparser 2019-06-13 20:20:43 +08:00
96529a4c1c update unit test cases for gjson/gcfg/gcompress/gparser 2019-06-13 19:41:43 +08:00
c7a729fe06 Merge branch 'master' of https://github.com/gogf/gf 2019-06-13 19:29:19 +08:00
23d404f681 add glog.Expose for glog; add Trim operation for glog.Logger.Write; fix issue in gjson.Remove for slice 2019-06-13 19:29:09 +08:00
887aeee2d4 Merge pull request #178 from piaohao/master 2019-06-13 19:09:55 +08:00
622dbfda31 完成garray测试覆盖率,达90%. 2019-06-13 17:02:30 +08:00
80cf3e833b 非test文件 恢复 原样 2019-06-13 12:41:20 +08:00
d305d25935 gparse测试用例 2019-06-13 12:30:35 +08:00
81502cfb6d gjson,gredis测试用例编写 2019-06-13 11:58:43 +08:00
5950a3fcc3 gpool unit test. 2019-06-13 11:42:51 +08:00
55f5e6d7aa improve gcharset 2019-06-12 23:50:37 +08:00
e2070e785c Merge pull request #23 from gogf/master
bug更新
2019-06-12 22:58:08 +08:00
905d5abed6 Merge pull request #171 from hailaz/master 2019-06-12 22:56:39 +08:00
211e06d04d Merge branch 'master' of https://github.com/gogf/gf into gogf-master 2019-06-12 22:56:23 +08:00
7aaf9e9228 fix issue in garray.Contains/New*ArrayFromCopy 2019-06-12 22:45:13 +08:00
a901e7177c 添加garray测试未完成 2019-06-12 22:37:17 +08:00
afc2bcfb28 gjson测试用例完善 2019-06-12 22:14:31 +08:00
8da204fbd8 remove test file of gchan 2019-06-12 21:10:49 +08:00
5aa3212fe1 gofmt geg/third 2019-06-12 21:06:57 +08:00
17d49510c4 add example for gcharset; fix issue in gxml 2019-06-12 21:02:00 +08:00
4af8ae1470 Merge branch 'master' of https://github.com/gogf/gf 2019-06-12 20:50:27 +08:00
3a5c660693 refract gcharset 2019-06-12 20:49:40 +08:00
9e65100a06 gcompress,gcfg,gparser模块测试用例编写 2019-06-12 19:22:02 +08:00
399e47c548 修改测试 2019-06-12 18:14:54 +08:00
abdf8e696c gtype unit test use AssertEQ replace Assert. 2019-06-12 17:52:30 +08:00
4a2e217625 Merge pull request #175 from 2892931976/gf-chj 2019-06-12 17:47:54 +08:00
52d0280137 Merge pull request #157 from wenzi1/master 2019-06-12 17:42:11 +08:00
5be9765eb7 add TODO for gfile.Search 2019-06-12 17:40:50 +08:00
cdb9488752 edit tests 2019-06-12 17:14:08 +08:00
0388113870 Merge branch 'master' of https://github.com/gogf/gf into gogf-master 2019-06-12 17:04:11 +08:00
c6dfb4d4f8 add some test 2019-06-12 16:52:02 +08:00
4a40b58b63 框架中增加字符集转换的标准库 2019-06-12 12:12:00 +08:00
407068a0bf add ghash basic test 2019-06-12 11:33:30 +08:00
165330ec68 gchan unit test 2019-06-12 11:21:10 +08:00
6e7d08fbfb fix issue in garray.Search 2019-06-12 10:11:54 +08:00
1ae77f56e5 添加garray测试代码 2019-06-11 23:32:08 +08:00
fccac04980 donator updates 2019-06-11 21:19:32 +08:00
469f9c7ce5 comment update for gflock 2019-06-11 21:14:48 +08:00
d6d37248f6 框架中增加字符集转换的标准库 2019-06-11 21:09:03 +08:00
cb1084b770 Merge branch 'master' of https://github.com/gogf/gf into develop 2019-06-11 20:59:17 +08:00
e6d4459992 comment update for gcache 2019-06-11 20:57:43 +08:00
1afb5a4bc5 框架中增加字符集转换的标准库 2019-06-11 19:41:26 +08:00
c124f172b2 Merge pull request #170 from piaohao/master 2019-06-11 19:32:57 +08:00
dbd4a7c1d4 框架中增加字符集转换的标准库 2019-06-11 19:28:24 +08:00
a4d30ef206 Merge branch 'qiangg_comment' into develop 2019-06-11 18:40:56 +08:00
0a616173ef add hash function for gview.ParseContent to improve performance in consurrent usage 2019-06-11 18:39:54 +08:00
597f210f85 去掉自动版本号 2019-06-11 18:11:47 +08:00
24bd83feb0 添加一些garray测试代码。 2019-06-11 18:11:17 +08:00
3855786905 gtype unit test 2019-06-11 17:49:29 +08:00
1f670a1ab2 gspath模块及gview模块测试用例编写 2019-06-11 17:49:05 +08:00
d97fda794c 框架中增加字符集转换的标准库 2019-06-11 17:25:30 +08:00
c034d25299 gspath模块及gview模块测试用例编写 2019-06-11 17:24:26 +08:00
dd6152fe8a 使用encoding库做字符集转换 2019-06-11 16:03:09 +08:00
485fe572ff 完成gset模块测试覆盖率90%以上。 2019-06-11 10:51:00 +08:00
15bf5d9a4d Merge pull request #2 from gogf/master
同步主线
2019-06-11 10:37:53 +08:00
08aa7c4e4c comment update for gcache 2019-06-10 23:54:40 +08:00
442c658be0 增加gset的测试支持
覆盖率达到85.1%
2019-06-10 23:38:19 +08:00
5aa8ce1c6b Merge branch 'master' into qiangg_comment 2019-06-10 23:09:32 +08:00
ad8ece68c6 Merge pull request #166 from zhongdalu/master 2019-06-10 23:03:27 +08:00
74525ba8f7 Merge pull request #164 from goflyfox/master
add gregex,gaes,gcrc32 test
2019-06-10 23:01:26 +08:00
46d46afaaf add gaes test:update assert type 2019-06-10 22:42:46 +08:00
13eb1150a5 fix issue in gfile.MainPkgPath 2019-06-10 21:32:40 +08:00
f98db6d21c comments for gcache update 2019-06-10 21:23:49 +08:00
zdl
c695dfd92e delete go version 2019-06-10 20:52:06 +08:00
zdl
ffd78d76e1 添加 genv 测试 1 2019-06-10 20:44:30 +08:00
zdl
e479c41667 添加 genv 测试 2019-06-10 20:24:11 +08:00
9f8c481992 Merge pull request #167 from piaohao/master 2019-06-10 20:21:41 +08:00
3320d12994 gcache测试用例完善 2019-06-10 19:58:00 +08:00
be6f522cf3 add gaes test4:optimize Encrypt Decrypt test 2019-06-10 19:53:07 +08:00
zdl
b0b6871bbb 添加 genv 测试 2019-06-10 19:50:45 +08:00
59ae6217cd add gaes test3: add encrypt assert content 2019-06-10 19:37:02 +08:00
334cd7ad51 add gregex test 2019-06-10 18:59:42 +08:00
501c3680d9 add gaes test 2019-06-10 17:26:32 +08:00
ebcc81c1ee Merge pull request #19 from gogf/master
日常更新
2019-06-10 16:12:51 +08:00
6814372a89 add gcrc test2 2019-06-10 15:55:43 +08:00
zdl
d5d14b7efc test gset TestSet_New 2019-06-10 15:49:43 +08:00
3a72686774 add gcrc test 2019-06-10 15:05:11 +08:00
aa73c5ed53 version updates 2019-06-10 09:01:19 +08:00
cc0a385c22 RELEASE updates 2019-06-09 11:56:58 +08:00
0b8c9713e6 improve grpool 2019-06-09 10:37:35 +08:00
e400a94ffb improving grpool 2019-06-09 10:33:16 +08:00
136ad3b0b5 add more unit test cases for hook feature of ghttp.Server 2019-06-08 19:38:46 +08:00
f9826104d8 improve gconv 2019-06-08 19:25:20 +08:00
c0c97b76fb update lead string position for glog 2019-06-07 23:00:34 +08:00
a908e4d4b1 fix issue in session initialization for ghttp.Server 2019-06-07 22:27:37 +08:00
e5c255200c 框架中增加字符集转换的标准库 2019-06-06 15:19:18 +08:00
00db4f5ed9 优先使用标准库的左字符集转换,标准库不支持的使用mahonia做转换 2019-06-06 15:11:32 +08:00
29ead3ff3e Merge pull request #4 from gogf/master
update
2019-06-06 14:52:12 +08:00
055074246e add TryLockFunc/TryRLockFunc for gmlock.Mutex 2019-06-05 23:50:24 +08:00
4bbe51fb4b add Close function for gtcp.Server/gudp.Server 2019-06-05 22:12:22 +08:00
841224372b update comment for gmlock 2019-06-05 21:58:27 +08:00
d1f0fa1a47 fix issue in concurrent safety for gview.Parse; rename glog.*fln functions to glog.*f 2019-06-05 20:22:57 +08:00
6fecf8bb01 update example 2019-06-05 18:41:11 +08:00
6d44f02a38 fix issue in internal/empty 2019-06-05 18:40:26 +08:00
1458e486d7 v1.6.17 2019-06-04 23:39:33 +08:00
dc29822e69 improve gtcp; add tls support for gtcp 2019-06-04 23:33:46 +08:00
dae7722da1 Merge branch 'master' into develop 2019-06-04 18:35:06 +08:00
16d978dc58 fix issue in gfile.MainPkgPath 2019-06-04 18:30:02 +08:00
5d3c154b45 improve gtcp 2019-06-04 18:26:32 +08:00
00a8ef63b6 improve gtcp 2019-06-03 23:53:59 +08:00
6ac437a3a5 merge master 2019-06-03 21:33:16 +08:00
33b24eba01 Merge pull request #147 from jroam/master 2019-06-03 21:00:26 +08:00
3a99c6e5f5 improve TryCatch function for gutil 2019-06-03 20:58:30 +08:00
4c5d2839bd donators updates 2019-06-03 20:34:19 +08:00
85b104bafa update benchmark test cases for gregex 2019-06-03 16:59:33 +08:00
74e5d03a78 Modify documentation 2019-06-03 16:55:18 +08:00
cc43324ede modify some instructions. 2019-06-03 16:41:27 +08:00
e4f9e1000d fix issue in status handler for ghttp.Server 2019-06-03 16:39:45 +08:00
62829b0698 modify some instructions 2019-06-03 16:39:39 +08:00
2551e990cb add gtime formats of 'U' 2019-06-03 16:30:34 +08:00
45c34319b5 Merge pull request #18 from gogf/master
日常更新
2019-06-03 09:28:29 +08:00
75dcc566b3 improve grpool to ensure atomicity for function Add 2019-06-03 09:09:40 +08:00
4f1047e853 add KeepAlive configuration for ghttp.Server 2019-06-02 22:19:52 +08:00
79f765c961 update example for gconv 2019-06-02 16:34:31 +08:00
eead2fad2c version updates 2019-06-02 13:46:05 +08:00
455c9e09ab README updates 2019-06-02 13:45:21 +08:00
4665c3565c README updates 2019-06-02 13:44:24 +08:00
7456b4b4ad README updates 2019-06-02 13:43:11 +08:00
32a6454065 README updates 2019-06-02 13:40:42 +08:00
c52640c672 README updates 2019-06-02 13:32:03 +08:00
d62ef17290 improve glog 2019-06-01 22:36:12 +08:00
216af6a662 updates glog 2019-06-01 20:34:57 +08:00
35d860427e add GetLastSql function for gdb 2019-06-01 20:31:29 +08:00
9206574bae add json tag most priority for gjson.New 2019-06-01 20:01:45 +08:00
9c6f54131f updates unit test cases for gdb,grpool 2019-06-01 19:37:00 +08:00
d67b95c593 add async logging feature for glog 2019-06-01 19:34:03 +08:00
2bf2f1b822 improve grpool 2019-06-01 15:11:32 +08:00
9ad94eccad add json output support for glog 2019-06-01 11:01:57 +08:00
6e8a900f25 fix issue in gjson/gparser in loading xml content; add *time.Time support for gdb 2019-06-01 00:02:05 +08:00
d9aa9e4480 improve grpool 2019-05-31 22:54:57 +08:00
fe74818a37 Merge pull request #3 from gogf/master
update
2019-05-29 16:32:20 +08:00
7335126064 mark deprecated for *fln functions of glog; fix issue in gfile.MainPkgPath in Windows 2019-05-29 15:18:12 +08:00
945dd71251 TODO updates 2019-05-29 11:53:10 +08:00
652aa29370 Merge branch 'master' of https://github.com/gogf/gf into develop 2019-05-29 11:26:27 +08:00
7034e2015e add FormatTo/LayoutTo functions for gtime; use custom ExpireFunc to close poool items when pool is close for gpool 2019-05-29 11:25:11 +08:00
0a890ad871 Merge branch 'qiangg_gdb_struct_inherit' into develop 2019-05-28 21:41:23 +08:00
b52bb1124e Merge branch 'master' into develop 2019-05-28 21:41:08 +08:00
41f33af51b add gconv.StructDeep/MapDeep functions for gconv; add struct inherit converting support for gdb 2019-05-28 21:41:00 +08:00
6b4763c7da Update README.MD
README updates.
2019-05-27 11:00:04 +08:00
be07889a45 Merge branch 'develop' into qiangg_gdb_struct_inherit 2019-05-24 20:07:20 +08:00
1b3243c09c comment update for gfile/gaes/gcrc32/gdes/gmd5/gsha1 2019-05-24 19:59:08 +08:00
fee1c9eccf Create FUNDING.yml 2019-05-24 19:24:25 +08:00
084f6c31cb comment updates for gfile 2019-05-23 21:58:04 +08:00
592bf76eb0 add browser mode support for ghttp.Client.Post 2019-05-23 21:20:46 +08:00
6f0aee1cc5 version updates 2019-05-23 20:43:09 +08:00
3575e20137 update Writer feature of glog 2019-05-23 20:34:06 +08:00
f69f529258 Merge branch 'master' into develop 2019-05-23 19:14:19 +08:00
3e7416ca7d add Skip for glog 2019-05-23 19:14:16 +08:00
cf324c5d8c Merge pull request #2 from gogf/master
update
2019-05-23 13:05:52 +08:00
9da883a50c comment updates 2019-05-23 11:49:33 +08:00
ea3e03aaba example updates for glog 2019-05-23 09:26:37 +08:00
8d9fdfeafc change parameter to unnecssary for glog.Header/Stdout; version updates 2019-05-22 21:43:56 +08:00
b2a8285ecb remove unnecessary pkg gscanner; update comment for gsmtp 2019-05-22 21:20:17 +08:00
162e8f7e51 comment updates for glog 2019-05-22 20:31:14 +08:00
6c658813cd refract glog to improve performance 2019-05-22 09:19:21 +08:00
0839ea385f refract glog to improve performance; 2019-05-21 23:57:03 +08:00
68fa235412 add gjson.GetJsonMap; refract glog to improve performance; 2019-05-21 21:52:17 +08:00
417ce4b470 Merge branch 'master' into qiangg_gdb_struct_inherit 2019-05-20 18:33:45 +08:00
48deaa5f57 improving package feature for gtcp 2019-05-20 08:59:16 +08:00
e9f7b8bc0c README updates 2019-05-18 14:55:15 +08:00
e31861af2e README updates 2019-05-18 13:52:18 +08:00
1af482d950 improve gfile.MainPkgPath 2019-05-17 21:37:48 +08:00
dd2436925b remove package feature for gudp, considering UDP protocal has its own border for message handling 2019-05-17 00:17:45 +08:00
b92b69564b improve gfile.MainPkgPath 2019-05-16 23:41:09 +08:00
a4fa163333 Merge pull request #1 from gogf/master
update
2019-05-16 15:08:08 +08:00
635d228c86 adding inherit struct support for gdb 2019-05-16 13:56:49 +08:00
75725db6fb remove gmlock for print of glog; add time.Time support for gdb 2019-05-15 23:53:48 +08:00
5cd8475143 add time.Time support for convertParam function of gdb 2019-05-15 16:47:39 +08:00
5629f37939 version updates 2019-05-14 22:38:03 +08:00
08ec04d8b6 fix issue in unit test case of gredis 2019-05-14 22:37:13 +08:00
c0b46f364a version updates 2019-05-14 22:02:09 +08:00
303d03d43c Merge pull request #17 from gogf/master
日常更新。
2019-05-14 21:36:00 +08:00
8c5f74e8bb add DoVar/ReceiveVar function for gredis 2019-05-14 21:34:38 +08:00
94832262e3 version updates 2019-05-13 22:37:31 +08:00
aefbfd52e9 add more example for gtree 2019-05-13 22:37:05 +08:00
f3f0689bd4 rename LinkMap to ListMap for gmap 2019-05-13 22:26:39 +08:00
5198d4c5fc add unit test cases for gtree.AVLTree/BTree 2019-05-12 22:56:13 +08:00
123f2d3e4e remove return value from RLockFunc/LockFunc for gset 2019-05-12 21:26:01 +08:00
3c750c3c92 copy treemap logics to new file 2019-05-12 21:22:07 +08:00
17b29cd19f improve performance for Map/Keys/Values functions for gmap; add unit test cases for gtree.RedBlackTree 2019-05-12 21:11:26 +08:00
cf1077bec4 add LinkMap for gmap package 2019-05-12 20:43:52 +08:00
4e2e4e95e0 add gmap.HashMap/TreeMap/AnyAnyMap for gmap; add unit test cases for TreeMap 2019-05-11 20:47:25 +08:00
61d64e7ae4 remove gvar.VarRead 2019-05-11 18:47:35 +08:00
883797c495 fix issue in gconv 2019-05-11 18:03:49 +08:00
0113971877 version updates 2019-05-11 17:59:56 +08:00
664b0c06a6 gjson updates 2019-05-11 17:56:14 +08:00
bd4c75a98e add AVLTree container for gtee 2019-05-10 22:29:06 +08:00
d35840409b add BTree container for gtree package 2019-05-10 21:31:35 +08:00
abaef9ba87 add gtree.RedBlackTree container 2019-05-10 13:38:06 +08:00
b15d8bdd2e adding gtree.RedBlackTree 2019-05-09 22:53:42 +08:00
718997327a add default value for gcfg.Get* functions; rename gconv.TimeDuration to gconv.Duration, and do corresponding changes to caller packages 2019-05-09 17:14:24 +08:00
fdfefbb94d add default value feature for gjson/gparser; update default value type for ghttp.Request 2019-05-09 14:19:27 +08:00
2b865a55ac update gjson/gparser 2019-05-08 22:04:36 +08:00
8138215597 comments update 2019-05-08 21:03:04 +08:00
7cc0c7a1cc rename map type of gmap; rename BatchSet/BatchRemove to Sets/Removes for gmap/gcache 2019-05-08 17:21:18 +08:00
50f561dbd2 Merge pull request #16 from gogf/master
日常更新
2019-05-08 13:36:03 +08:00
4c647aaa19 refacting gmap 2019-05-07 22:28:34 +08:00
48b1d616c5 update code format for gtime 2019-05-07 10:22:50 +08:00
693c37d6d6 Merge pull request #116 from jroam/master
add some var flags gtime.format
2019-05-07 10:18:22 +08:00
d525c04826 公开几个常用功能方法 2019-05-06 15:03:14 +08:00
c170edbdfc add ghttp.Request.GetUrl to get current request URL 2019-05-06 13:52:34 +08:00
66e40155a9 add http/https scheme for log of ghttp.Server; add transport setting to ignore tls cert for ghttp.Client; version updates 2019-05-06 09:35:39 +08:00
59ad1a9b00 fix issue in incorrect every running logics of gcron.AddOnce 2019-05-05 22:57:13 +08:00
a5b536e218 去掉不常用参数e 2019-05-05 22:49:52 +08:00
0e6c2e790d copy mysql driver to third folder 2019-05-04 23:02:07 +08:00
5761e73061 优化一些参数性能 2019-05-04 18:14:05 +08:00
34c761e9db rename mysql driver from 'mysql' to 'gf-mysql' to avoid multiple imports error 2019-05-04 00:25:02 +08:00
87e3813636 remove go-sql-driver-mysql from third, add Register function to manually register 'mysql' driver 2019-05-04 00:10:02 +08:00
361ff0315c version updates 2019-05-03 17:04:42 +08:00
2bb227d058 update gtcp exxamples 2019-05-03 15:47:25 +08:00
99dc69e839 update gtcp exxamples 2019-05-03 13:28:27 +08:00
d78fde8099 edit a func's name 2019-04-30 22:31:52 +08:00
5d0c8956d6 edit some errs and fmt some codes 2019-04-30 11:45:26 +08:00
c9537af062 add package feature for gudp; gtcp updates 2019-04-29 23:54:47 +08:00
dfb5b3a8ce Merge pull request #15 from gogf/master
日常更新
2019-04-29 14:30:24 +08:00
a177e44583 edit some infos 2019-04-29 14:29:33 +08:00
7ae03729f3 add package support for gtcp 2019-04-28 23:55:23 +08:00
898ec21a25 del mod contents of 'go1.12' 2019-04-28 23:00:07 +08:00
6d7d8dec02 add var 'e' of gtime.format 2019-04-28 22:53:03 +08:00
ea7e2ec5ec remove go version limit in go.mod 2019-04-28 21:10:22 +08:00
a5b8e2aa2f add var 'w' of gtime.format 2019-04-28 18:12:43 +08:00
123333d9c2 edit some info 2019-04-27 22:59:06 +08:00
0c4fa1d96a fmt files 2019-04-27 22:50:44 +08:00
e5805e8c69 Merge branch 'master' of https://github.com/jroam/gf 2019-04-27 22:44:59 +08:00
bf2d45a012 add gtime.format var 't' 2019-04-27 22:37:36 +08:00
a7122788b1 add 'W' to gtime.format func(doing) 2019-04-26 17:52:45 +08:00
237c58f2b0 Merge branch 'master' of https://github.com/gogf/gf 2019-04-26 13:46:47 +08:00
efa23e4a1d README updates 2019-04-26 13:46:42 +08:00
c109cee7ef Merge pull request #115 from touzijiao/master
Merge pull request #115 from touzijiao/master
2019-04-26 13:44:51 +08:00
e111d39c54 测试文件 2019-04-26 10:38:27 +08:00
66306464e1 add Pop/Pops functions for gset 2019-04-26 08:57:48 +08:00
dd34ac1722 add build-in function 'eq/ne/lt/le/gt/ge' for gview to replace the same functions in stdlib 2019-04-25 23:23:24 +08:00
34cb222b33 remove temprary function map parameter for gview when parsing template file and content 2019-04-25 22:14:20 +08:00
a0276f7e81 add param 'z' to gtime.format func 2019-04-24 23:12:48 +08:00
d39ef156de garray updates 2019-04-24 22:23:32 +08:00
f464dc7fb8 add NewFrom/NewIntSetFrom/NewStringSetFrom functions for gset; add more example for gset 2019-04-24 18:52:24 +08:00
d29b27a5df add Merge/Sum functions for gset 2019-04-24 18:15:50 +08:00
5346ca9046 use golint checked 2019-04-24 18:08:10 +08:00
6d5b552bb7 add param of 'N','s' to gtime.format 2019-04-24 18:03:29 +08:00
aadc6aa504 Merge pull request #114 from proptypes/master
fix: #112
2019-04-24 14:07:37 +08:00
e8c3dfa13e fix: #112
closes #112
2019-04-24 11:31:13 +08:00
836d62f4aa Merge pull request #14 from gogf/master
日常更新
2019-04-24 09:34:50 +08:00
9eea93cc6e change param type from string to interface{} for ghttp.ClientRequest 2019-04-23 20:12:44 +08:00
308cb55b6b version updates 2019-04-23 19:44:28 +08:00
75ada78f8f remove parameter bind from ghttp.RouterGroup.Bind 2019-04-23 19:39:35 +08:00
ecd86e3a12 add layout example for package gview 2019-04-23 19:11:38 +08:00
c1aa5eb717 Merge pull request #98 from qq976739120/gmap-test
新增测试方法
2019-04-23 18:56:58 +08:00
d2fed1198b Merge pull request #110 from jroam/master
增加gtime包下,format方法能直接格式化星期的数字型的值
2019-04-23 18:54:43 +08:00
a9f9261dbd add gregex.ReplaceFuncMatch/ReplaceStringFuncMatch functions for package gregex 2019-04-23 14:15:12 +08:00
161e0d6e97 edit var name to "weekMap" 2019-04-23 09:59:13 +08:00
3efe511f42 优化星期英文值和数字值的格式化功能 2019-04-22 23:13:35 +08:00
5d04c2e50a fix issue in gfile.MainPkgPath 2019-04-22 22:33:11 +08:00
7b26b7ea4c fix issue in gstr.Chr 2019-04-22 21:37:11 +08:00
9d1063c6b2 Merge branch 'master' of https://github.com/gogf/gf 2019-04-22 15:48:16 +08:00
5ff7632d32 add support of layout feature for gview; fix issue in gstr.Chr 2019-04-22 15:47:59 +08:00
e6fb41504c 简写"w"参数的注释,增加周六值测试 2019-04-22 14:10:46 +08:00
a800f731dd 优化格式化星期值性能 2019-04-22 13:55:53 +08:00
f1a9fbb74e Merge branch 'master' of https://github.com/jroam/gf 2019-04-22 10:51:40 +08:00
cf81a73526 增加gtime包下,format能直接格式化星期的数字型的值 2019-04-22 10:51:24 +08:00
65036fffe8 Merge pull request #13 from gogf/master
日常更新
2019-04-22 10:49:28 +08:00
a69934a7e3 添加星期值的int形式 2019-04-21 19:36:06 +08:00
9400457bf2 Merge pull request #107 from wenzi1/master
add encoding package unit test
2019-04-19 18:29:11 +08:00
5060329721 Merge branch 'master' of https://github.com/wenzi1/gf 2019-04-19 17:11:10 +08:00
07ab1d60e8 add encoding package unit test 2019-04-19 17:04:43 +08:00
7377a82e19 Merge pull request #8 from gogf/master
Merge pull request #80 from wenzi1/master
2019-04-19 12:06:00 +08:00
f82e3ac808 Merge pull request #80 from wenzi1/master
增加gbinary单元测试
2019-04-18 22:50:24 +08:00
1a6cd1de04 Merge pull request #7 from gogf/master
update latest code
2019-04-18 20:22:17 +08:00
90e6f685b7 Merge pull request #12 from gogf/master
日常更新
2019-04-18 17:55:19 +08:00
0fc825dac1 add gcompress packge unit test 2019-04-18 12:34:01 +08:00
dbb27efe3e rename io to writer for glog.Logger 2019-04-18 09:11:14 +08:00
2d3d2e783e add default writer for glog to be integrated with other package; comments update for glog 2019-04-17 23:50:37 +08:00
6ae1defa35 Merge pull request #6 from gogf/master
pull
2019-04-17 16:56:18 +08:00
d55e77fb90 README updates 2019-04-17 09:50:57 +08:00
7fb9ae71e7 Merge pull request #103 from jroam/master
增加gfile模块的测试代码
2019-04-17 09:47:51 +08:00
9419555149 add BindFuncMap funtion for gview; README updates 2019-04-17 09:46:31 +08:00
ab634f8beb del some test code 2019-04-16 23:15:22 +08:00
9503b80d57 edit tempdir 2019-04-16 23:07:53 +08:00
bc870226e5 Merge pull request #11 from gogf/master
日常更新
2019-04-16 22:52:03 +08:00
7e106ae011 Merge pull request #81 from pibigstar/master
add the crypto test
2019-04-16 22:38:26 +08:00
958e00e231 fmt test code file 2019-04-16 22:14:42 +08:00
ab187d225d edit yml 2019-04-16 22:12:29 +08:00
9f5711d41d edit testpackage's name 2019-04-16 22:11:44 +08:00
cdb2773127 Merge branch 'master' into test_gfile 2019-04-16 18:35:25 +08:00
d12532ccc1 set a definite value for an assertion #66 2019-04-16 15:49:42 +08:00
53e9f05a10 make the details perfect #66 2019-04-16 15:49:42 +08:00
60e7ab95bc increase coverage for crypto #66 2019-04-16 15:49:42 +08:00
ae552e2b46 increase coverage for crypto #66 2019-04-16 15:49:42 +08:00
145b52f343 increase coverage for crypto #66 2019-04-16 15:49:42 +08:00
57b8fac0d5 add the crypto test 2019-04-16 15:49:42 +08:00
Jay
16a4a5ba46 Gmap测试修改 2019-04-16 14:28:25 +08:00
3d37c83532 Update TODO.MD 2019-04-15 23:49:47 +08:00
b02fa701b8 add test_gfile test of ci 2019-04-15 23:31:17 +08:00
37b7c82c64 Merge branch 'master' into test_gfile 2019-04-15 23:30:21 +08:00
1deb27df06 Merge branch 'master' of https://github.com/gogf/gf into gogf-master 2019-04-15 23:24:38 +08:00
84ed3d5767 add move and rename testfun 2019-04-15 23:14:48 +08:00
fc909a3db2 travis ci updates 2019-04-15 23:06:42 +08:00
2f7d4cd80d fix issue in Add function for gtype.Float32/Float64 2019-04-15 22:55:12 +08:00
03ccbf3613 edit TestStat fun 2019-04-15 22:52:19 +08:00
1868465319 edit TestTruncate fun 2019-04-15 22:47:09 +08:00
ff473e2fcc edit ci yml 2019-04-15 22:17:58 +08:00
a3c38eec86 edit ci yml 2019-04-15 22:12:28 +08:00
2015c847e8 fmt test file 2019-04-15 18:14:38 +08:00
1b583ed984 pull ci yml 2019-04-15 15:41:51 +08:00
11191c746a edit test 2019-04-15 15:34:35 +08:00
abedd3c5bf edit test 2019-04-15 15:30:44 +08:00
501ba5135b edit test 2019-04-15 15:27:49 +08:00
a76c98c348 暂时去掉search测试 2019-04-15 14:01:09 +08:00
9dcdc1a339 edit ci yml 2019-04-15 13:47:25 +08:00
6d1f386203 edit travis.yml 2019-04-15 11:49:07 +08:00
ff9bbf0a49 edit test 2019-04-15 11:41:05 +08:00
66e24c8d40 Merge branch 'master' into test_gfile 2019-04-15 11:26:11 +08:00
34f117c631 Merge pull request #9 from gogf/master
日常更新
2019-04-15 11:22:16 +08:00
21f2f16889 go test 2019-04-14 23:21:03 +08:00
f6fa7c422d 测试小调整 2019-04-14 22:41:11 +08:00
6903b84bb6 调整测试文件生成目录 2019-04-13 23:00:57 +08:00
0978b8fb4f 初步完成测试代码重构 2019-04-13 18:27:01 +08:00
d014583e88 重构测试代码中 2019-04-13 13:14:30 +08:00
a747f51b9d 重构测试代码 2019-04-12 18:15:31 +08:00
8300885ab6 Merge pull request #5 from gogf/master
update gtest
2019-04-12 17:59:02 +08:00
Jay
b489eed4ef 新增测试方法 2019-04-12 10:59:05 +08:00
0b57771d76 用fmt格式化代码 2019-04-12 09:47:55 +08:00
8a32a8271c fix issue in empty router group error of ghttp.Server 2019-04-12 00:19:15 +08:00
c3e716dafd Merge branch 'master' of https://github.com/gogf/gf 2019-04-11 23:07:28 +08:00
119a11eb8d README updates 2019-04-11 23:07:22 +08:00
e464d14842 Merge pull request #85 from qq976739120/gmap-test
Gmap 测试添加
2019-04-11 21:59:00 +08:00
Jay
905f46359a Gmap string-string 类型测试提交 2019-04-11 17:50:26 +08:00
Jay
8e7bf1f908 bug 修复 2019-04-11 17:46:19 +08:00
Jay
3a1524ae6d Gmap 初测完成 2019-04-11 17:33:52 +08:00
5c04befea3 edit ci yml file 2019-04-11 16:39:20 +08:00
ae3584cdff edit ci 2019-04-11 16:27:47 +08:00
a60578c82c edit ci 2019-04-11 16:23:29 +08:00
74558a500c edit c1 2019-04-11 16:17:44 +08:00
d963d8c8c1 testci 2019-04-11 16:13:56 +08:00
853892c24f config ci 2019-04-11 16:05:33 +08:00
641f939e3d edit test 2019-04-11 15:57:48 +08:00
af77504eaf 修改测试 2019-04-11 15:49:00 +08:00
62649d6468 config ci 2019-04-11 15:22:29 +08:00
bb914e605e 配置ci 2019-04-11 15:08:17 +08:00
62ee88bbfa Update .travis.yml 2019-04-11 14:45:47 +08:00
99c964bb4a 添加ci配置文 2019-04-11 11:02:43 +08:00
09ceaef3e9 配置ci 2019-04-11 10:59:53 +08:00
a82900af55 对分支添加ci检查 2019-04-11 10:03:47 +08:00
5ef589f31a Merge pull request #8 from gogf/master
合并前更新。
2019-04-11 09:56:01 +08:00
cd719f134d 测试率达到82.9% 2019-04-11 09:47:51 +08:00
f69eb219b5 version updates 2019-04-11 09:43:17 +08:00
0ffe17ee3d revert SearchArray/InArray functions for gstr 2019-04-11 09:26:52 +08:00
4ac647a215 gcmd updates 2019-04-11 09:05:27 +08:00
872536c035 Merge branch 'master' of https://github.com/gogf/gf 2019-04-10 23:09:59 +08:00
c7a6a6fff0 improve grand.Intn; README updates 2019-04-10 23:09:38 +08:00
2534655bc8 测试覆盖率达到76.3% 2019-04-10 22:16:16 +08:00
0cb82d70fd Merge pull request #96 from aloncn/master
update unit test for gring
2019-04-10 21:42:25 +08:00
acac5a2ad6 version updates 2019-04-10 18:33:51 +08:00
9ec15ad2ca fix issue in repeated rand value of grand.Intn 2019-04-10 18:33:12 +08:00
a9b7d56d0b 测试覆盖率达到63.1% 2019-04-10 17:54:28 +08:00
e9ca1eb538 完成第一遍功能测试 2019-04-10 16:45:33 +08:00
0532800895 添加测试代码 2019-04-10 16:22:29 +08:00
ad50ca6e60 update unit test for gring 2019-04-10 14:26:21 +08:00
c1ad999c25 添加测试 2019-04-10 13:45:42 +08:00
4accd1264d 添加内容测试 2019-04-10 13:39:36 +08:00
e1f3da3aa8 去掉mod设置的版本信息 2019-04-10 11:31:24 +08:00
4bf9a7950b Merge branch 'master' into test_gfile 2019-04-10 11:28:57 +08:00
8285c31bf1 Update unit test for gring 2019-04-10 10:44:40 +08:00
230be66fa9 update unit test for gring 2019-04-10 10:13:56 +08:00
428d7ec94a Merge pull request #1 from gogf/master
同步主库
2019-04-10 09:54:16 +08:00
2c53f934c9 Merge branch 'master' of https://github.com/gogf/gf 2019-04-10 09:50:29 +08:00
08785cb272 update unit test cases of gjson 2019-04-10 08:54:49 +08:00
bd0207c938 update unit test for gring 2019-04-10 01:16:45 +08:00
6fad737617 update unit test cases of gjson/gparser 2019-04-10 01:00:51 +08:00
af4148d985 Merge branch 'master' of github.com:aloncn/gf 2019-04-10 00:37:22 +08:00
922eaf4d42 Update unit test for gring 2019-04-10 00:37:06 +08:00
4b5153950f Add unit test for gring 2019-04-10 00:37:06 +08:00
78010d2bd7 Update unit test for gring 2019-04-10 00:22:10 +08:00
6cc0017826 测试空的断言。 2019-04-09 23:25:20 +08:00
8a9131c3dc Merge pull request #7 from gogf/master
日常更新
2019-04-09 23:16:18 +08:00
85c2ed1bf2 improve nil checks for gtest.Assert* 2019-04-09 23:12:22 +08:00
aca1df634d 添加测试文件内容相关函数。 2019-04-09 23:11:04 +08:00
053a3c1a53 add unit test 2019-04-09 19:12:48 +08:00
429aa90e0d Merge branch 'master' of github.com:aloncn/gf 2019-04-09 18:42:16 +08:00
4f792b347d Add unit test for gring 2019-04-09 18:40:48 +08:00
468c315087 Merge pull request #4 from gogf/master
update 1.6
2019-04-09 17:36:40 +08:00
6d8ced21b9 Add unit test for gring 2019-04-09 17:27:58 +08:00
b3d5fc149e add unit test 2019-04-09 17:27:11 +08:00
Jay
277b7a4536 测试修改 2019-04-09 15:06:12 +08:00
43886511b9 add unit test 2019-04-09 12:28:21 +08:00
8b3eb5b02d version&release updates 2019-04-09 00:15:48 +08:00
054ef87886 继续添加文件测试. 2019-04-08 23:13:28 +08:00
Jay
39c65d9e9a 测试添加 2019-04-08 17:49:15 +08:00
Jay
b97bbbfa3d 测试添加 2019-04-08 17:32:07 +08:00
Jay
ace6ba8096 Gmap 测试添加 2019-04-08 17:02:57 +08:00
a2b87d84e9 添加gfile测试代码,10% 2019-04-07 22:46:14 +08:00
b90d61b27c README updates 2019-04-07 22:03:04 +08:00
85606e3e7e README updates 2019-04-07 21:59:28 +08:00
1fc85d49bd gtime updates; README updates 2019-04-07 21:49:24 +08:00
a5cfb4e638 Merge pull request #82 from hailaz/master
Add gtime unit test
2019-04-07 21:27:29 +08:00
38754bf062 Merge pull request #78 from youyixiao/youyixiao_gogf
gregex_unit_test
2019-04-07 21:23:03 +08:00
f1818ed2ff 补充gtime_format的覆盖测试。 2019-04-06 23:53:06 +08:00
352ad17715 避免pr时造成gtime文件的更改。 2019-04-06 22:56:47 +08:00
e50b8d9632 移除gtime unit test中注释掉的函数。 2019-04-06 22:50:37 +08:00
2107061a46 Add gtime unit test 2019-04-06 22:48:47 +08:00
61f57b4895 update comments of gstr 2019-04-06 21:31:01 +08:00
d34273abff 同步master 2019-04-05 23:53:43 +08:00
0aff0f0362 同步master 2019-04-05 23:49:20 +08:00
68949b69bc update example of gtime 2019-04-05 23:31:14 +08:00
c56c77d3a1 fix issue in G&j char format 2019-04-05 23:13:47 +08:00
dc82ce395a comment updates for gcfg 2019-04-05 00:23:59 +08:00
fd63a2209b 增加单元测试 2019-04-04 23:31:17 +08:00
2a29483456 comment fix 2019-04-04 23:24:27 +08:00
4f10562980 remove limit for gdb.Model.One 2019-04-04 23:22:09 +08:00
a26ec37f59 Merge pull request #3 from gogf/master
更新
2019-04-04 23:17:09 +08:00
779ad93bcb 参数为nil时的特殊处理 2019-04-04 23:02:00 +08:00
1ec0219473 add gbinary unit tests 2019-04-04 23:00:21 +08:00
6863928b06 !18 限制下查询单条记录查询条数
Merge pull request !18 from 一墨染尽青衣颜/master
2019-04-04 22:55:13 +08:00
aa4dca11f0 fix issue in gtime; update comment for g/gtest 2019-04-04 22:52:56 +08:00
dc6ab820ce 修复解析日期函数(parseDateStr)对"02.jan.2006"格式日期解析异常的问题。
更正函数(isNumeric)的注释。
2019-04-04 17:57:17 +08:00
be0fa4d60b gregex_unit_test 2019-04-04 17:45:04 +08:00
a86d2272af gregex_unit_test 2019-04-04 16:15:50 +08:00
61a67892ac Merge pull request #6 from gogf/master
同步最新代码
2019-04-04 11:37:58 +08:00
388d5954cb Merge pull request #2 from gogf/master
update unit test
2019-04-04 10:56:01 +08:00
08550d413e update unit test cases for gcfg 2019-04-04 09:18:43 +08:00
b89294561b add gmap.SetIfNotExistFunc/SetIfNotExistFuncLock for gmap; update comment of gmap; update instance feature of gcfg 2019-04-03 23:39:31 +08:00
20977558cc Merge pull request #1 from gogf/master
更新代码
2019-04-03 16:47:16 +08:00
630d8fdb43 add Instance function for gcfg 2019-04-03 09:59:15 +08:00
c4c7e6caf4 gofmt 2019-04-03 00:03:46 +08:00
8a8fea1257 v1.5.23 2019-04-02 23:40:20 +08:00
0e0f297a3f add consurrent safety parameter for gjson.Load/LoadContent; fulfil data type auto-check logics for gjson.LoadConetnt; update comment of gjson/gparser 2019-04-02 23:33:27 +08:00
fd2c0f2b24 Merge pull request #59 from wenzi1/master
修复gxml字符集转换的并发安全问题
2019-04-02 17:43:19 +08:00
ecc6e3888d up 2019-04-02 17:10:21 +08:00
47c073aaf3 add Clear function for gcfg; mark Reload function of gcfg as deprecated; update unit test for gins 2019-04-02 16:08:46 +08:00
07476a4349 add instance management feature for gdb/gredis; add customized configuration content management feature for gcfg; update gjson for data content type check 2019-04-02 14:37:46 +08:00
817148f3a1 增加单元测试 2019-04-01 18:32:37 +08:00
bd4271cd8c 增加gxml的单元测试 2019-03-31 23:36:11 +08:00
b1804fc346 TODO updates; version updates 2019-03-31 21:13:11 +08:00
a3886c2179 fix issue in RemoteAddr of gudp.Conn 2019-03-31 20:58:31 +08:00
f258b5bf1c change logging level from error to debug in route regitsry for struct for ghttp package; handle possible overrange usage of r.URL.Path in ghttp.Server 2019-03-31 20:52:30 +08:00
a05361011f 修复并发安全问题,改为如果非UTF8字符集则先做字符集转换 2019-03-30 23:53:42 +08:00
6b34a77251 修复并发安全问题,改为如果非UTF8字符集则先做字符集转换 2019-03-30 23:39:07 +08:00
afb1adee3d version updates 2019-03-28 17:58:33 +08:00
22fa7a37f3 add UseNumber support for gjson; add more unit test cases for gjson 2019-03-28 17:51:05 +08:00
6a58bfc574 add stdout printing support for ghttp.Server logging 2019-03-28 09:34:16 +08:00
64124c60fc add fatal error when no router set or statis feature enabled for ghttp.Server 2019-03-27 16:23:35 +08:00
9a0066de62 add file/folder search support for gcfg/gview in order of envpath/pwdpath/binpath/mainpath; add gfile.Search function 2019-03-27 11:48:53 +08:00
22c7c7403b fix issue in basic http auth check for server side 2019-03-27 09:16:23 +08:00
83db8e4b15 add wss example for websocket 2019-03-22 17:26:39 +08:00
25f2e121e7 version updates 2019-03-22 15:09:22 +08:00
1325a145d8 fix issue in config auto reload of gcfg 2019-03-22 15:08:43 +08:00
9bc49c0b29 add logging for error in ghttp.Request 2019-03-22 14:31:02 +08:00
8e84e5b0f3 update comment of gredis 2019-03-22 11:22:03 +08:00
a42e6b0c45 ignore private attribute of struct in gconv/gvalid 2019-03-22 10:12:43 +08:00
51bb7a9854 ignore private attribute of struct in gconv/gvalid 2019-03-22 10:12:15 +08:00
62580b5719 add examples for gredis 2019-03-21 23:51:57 +08:00
15cfd5ce5c add g.Export 2019-03-21 18:21:53 +08:00
11c89c4090 fix issue in GetOrSetFuncLock of gmap/gcache; add gutil.IsEmpty/Export functions 2019-03-21 18:20:20 +08:00
4a12cb9f27 version updates 2019-03-21 10:52:12 +08:00
32f575eddd update unit test of gins 2019-03-21 10:36:24 +08:00
fbb4cb3b1c add more unit test cases for gredis 2019-03-21 10:04:53 +08:00
c9d2d5e8ab add configuration support for maxIdle/maxActive/idleTimeout/maxConnLifetime of gredis; update gjson.Append/Len functions; add more unit test cases for gredis/gins/gstr/gjson 2019-03-21 00:14:23 +08:00
93763192f2 version updates 2019-03-19 17:55:02 +08:00
80e0eae6b0 add TLSConfig support for ghttp.Server 2019-03-19 17:48:37 +08:00
4e3d735b90 add logging for gcron 2019-03-19 13:58:18 +08:00
60e5a7da28 add more unit test cases for grand/gstr 2019-03-18 23:52:25 +08:00
997b5ba889 README updates 2019-03-18 14:10:30 +08:00
b3e7ca1963 travis updates 2019-03-18 14:05:46 +08:00
5a82d695c1 mv greuseport to a new repo 2019-03-18 13:56:16 +08:00
64a0427150 travis updates 2019-03-18 13:34:51 +08:00
f6aafc1d6b Merge pull request #5 from gogf/master
更新数据库操作
2019-03-18 10:00:23 +08:00
bca5532df8 version updates 2019-03-17 22:36:58 +08:00
72eeadd9aa Merge branch 'master' into develop 2019-03-17 22:31:15 +08:00
0af55794f6 add more unit test cases for gdb 2019-03-17 22:26:41 +08:00
25a6c53533 add unit test cases for gfsnotify 2019-03-15 14:54:01 +08:00
9f9172c775 update unit test cases of package gconv 2019-03-15 09:05:56 +08:00
320e0db417 fix int-overflow issue in gconv.String when converting int64 to string; add more unit test cases for package gconv 2019-03-15 00:22:39 +08:00
cb8362d447 update unit test cases of package gins 2019-03-14 23:28:56 +08:00
45a83fc53c unit test cases update for package gins 2019-03-14 00:23:46 +08:00
3411bd1c1d merge master 2019-03-13 22:41:00 +08:00
6ab0a77364 add more defaulr searching paths for g.Config() 2019-03-13 22:12:59 +08:00
281bae4116 unit test cases update for package gins 2019-03-13 09:11:50 +08:00
218c692fe0 update unit test cases 2019-03-12 23:56:09 +08:00
fa69b581e1 update unit test cases 2019-03-12 23:50:30 +08:00
bd0baceeca update unit test cases 2019-03-12 23:47:27 +08:00
e71c837472 update unit test cases 2019-03-12 23:45:44 +08:00
782aaabd07 add more unit test cases for gins; update text/template for gview 2019-03-12 23:26:10 +08:00
9014325a7c test 2019-03-12 10:54:38 +08:00
8ae9276732 add disable cache feature option for package gspath 2019-03-12 00:24:31 +08:00
79a3aa5916 add more unit test cases for package gvalid 2019-03-11 22:58:21 +08:00
733c5db228 version updates 2019-03-11 22:33:43 +08:00
6171c621a7 fix issue in missing customed error messages in package gvalid 2019-03-11 22:33:20 +08:00
fc11856a28 Merge pull request #4 from gogf/master
日常更新
2019-03-11 17:43:06 +08:00
802568856c version updates 2019-03-11 16:35:44 +08:00
127fb67185 add session id check in session object initialzing 2019-03-11 16:33:51 +08:00
5db039bbce gdb.Model updates, rename Alterable function to Safe, change gdb.Model alterable in default, update Where function to support more cases when using map as its param, add more unit test cases for gdb.Model 2019-03-11 16:14:55 +08:00
b0726b9733 Merge pull request #3 from gogf/master
日常更新
2019-03-10 22:38:15 +08:00
8a3365d18e ghttp comment updates 2019-03-10 00:39:34 +08:00
2ae5b1a4f8 add more unit test cases for ghttp.Server 2019-03-10 00:35:03 +08:00
f9515d7126 version updates 2019-03-09 10:20:11 +08:00
b56679a97c revert g.Map to map[string]interface{}, add g.MapAnyAny 2019-03-09 10:17:21 +08:00
d1b123964a README updates 2019-03-08 21:07:18 +08:00
991f7c4958 ghttp.Response updates 2019-03-08 19:03:22 +08:00
770619c39e update unit test cases og gcron 2019-03-08 18:27:11 +08:00
4ad5450b80 gtest updates; remove one unit test case of gconv.Struct 2019-03-08 18:18:29 +08:00
49ce7fe885 fix data race issue of gqueue 2019-03-08 17:54:50 +08:00
e66f63262b add more unit test cases for ghttp.Server 2019-03-08 17:31:30 +08:00
94bd5da68a add map key operator support in Where function for gdb 2019-03-08 11:12:52 +08:00
3eee95caf2 VERSION updates 2019-03-08 10:35:35 +08:00
5c638c630a add select in support for slice type of arguments in Where function of gdb 2019-03-08 10:33:36 +08:00
a6ec9d7a1c update gdb.Model.Where, orverwrite args if multiple calls Where function 2019-03-08 10:18:38 +08:00
374c70c0e3 README updates 2019-03-08 09:03:32 +08:00
40771066d4 travis updates 2019-03-08 08:48:53 +08:00
22a7ef43ce Merge branch 'master' into develop 2019-03-08 00:21:52 +08:00
05f22d1cee README updates 2019-03-08 00:15:57 +08:00
ebf56a86ab add Alterable function for gdb.Model 2019-03-07 23:53:56 +08:00
2ba59e8943 fix issue in gtime convert in package gconv; add Structs/Scan functions for gdb.Model; add GetStructs/GetScan functions for gdb.Base and gdb.TX 2019-03-07 23:36:45 +08:00
4e4ea25e7f Merge pull request #2 from gogf/master
第一次同步
2019-03-07 17:35:22 +08:00
83be1de04c remove error returns from router registry functions of WebServer; add more unit test cases for WebServer 2019-03-06 15:21:00 +08:00
c8251ed82f g.Map updates 2019-03-06 10:38:50 +08:00
2335ea0c4d merge master 2019-03-05 21:07:54 +08:00
5d874e9063 add example code for gconv.Map; comment updates of gdb 2019-03-05 17:52:34 +08:00
f2c080d25f update ghttp.Response 2019-03-05 17:16:21 +08:00
975da97b4a fix issue in empty check for nil attribute of struct in package 'empty';add set-cookie support for 302 status in ghttp.Server 2019-03-05 17:06:37 +08:00
37617589a6 add more unit test cases for ghttp.Server 2019-03-04 23:51:44 +08:00
5c9f0db903 version updates 2019-03-04 23:37:01 +08:00
28abf0c175 add ReplaceI/ReplaceIByArray/ReplaceIByMap case-insensetive replacing functions for gstr 2019-03-04 23:35:06 +08:00
13749feab4 add json features '-' and 'omitempty' for gconv.Map, like package 'json' from stdlib; add internal package 'empty', to check empty variable 2019-03-04 22:59:29 +08:00
c1e77b7e09 update example of gtcp 2019-03-03 20:40:36 +08:00
962a5e93f7 update example of gtcp 2019-03-03 20:20:31 +08:00
c3b9b8d5ae gconv updates 2019-03-03 00:53:35 +08:00
1ad076c522 travis updates 2019-03-03 00:31:16 +08:00
b01777fcd1 travis updates 2019-03-03 00:28:32 +08:00
55a5532c2e gdb updates 2019-03-03 00:14:20 +08:00
adb928941a issue template updates 2019-03-02 00:02:05 +08:00
f92c1fc527 update CORS feature of ghttp.Response; add more unit cases for ghttp.Server 2019-03-01 23:45:55 +08:00
3ae7279ebc issue template updates 2019-03-01 14:01:05 +08:00
66287c2d0e update the admin feature and unit test cases of ghttp 2019-02-28 23:57:20 +08:00
5d37626981 hot fix issue in router registry 2019-02-28 14:07:00 +08:00
d0ed3b979d release updates 2019-02-28 10:28:09 +08:00
fa256aec9f add example for layout using template engine; fix issue with config error output in gview when no config used 2019-02-27 22:53:39 +08:00
86834c5a15 version and comment updates 2019-02-27 22:17:09 +08:00
c2046157d6 add ghttp.Request.GetRawString function 2019-02-27 21:17:56 +08:00
cdb2cc89c0 remove password for unit test of gdb 2019-02-27 12:51:48 +08:00
4964c09a77 add more unit test cases for gdb 2019-02-27 12:38:12 +08:00
ef34b2c9ce gdb updates, add batch operation support for Insert/Save/Replace, change list param type from List to interface{} for Batch* functions 2019-02-27 09:38:10 +08:00
9afe242293 Merge branch 'master' into qiangg_gdb_map 2019-02-27 08:52:50 +08:00
136d93d373 add donate for gitee 2019-02-26 23:39:09 +08:00
3102cec5b8 issue template updates 2019-02-26 23:06:14 +08:00
e28eb9da04 add issue template 2019-02-26 22:58:52 +08:00
754ed86dfb add issue template 2019-02-26 22:56:52 +08:00
e352b07055 add issue_template 2019-02-26 22:51:30 +08:00
fdea242b50 Merge branch 'master' into develop 2019-02-26 22:23:24 +08:00
7058e4f2c4 fix issue of router group in auto-adding 'index' router for controller and object 2019-02-26 22:21:57 +08:00
704a5dbd73 fix issue of "memory leaks" in gfpool 2019-02-26 17:52:50 +08:00
fbd4ce8c2e fulfil unit cases of ghttp.Cookie/Session 2019-02-26 17:17:11 +08:00
cb3ce71cdc fix issue in ghttp.Session 2019-02-26 14:33:01 +08:00
7f44f2f5e4 gdb updates 2019-02-26 14:23:29 +08:00
66efbe63f0 add struct support for *Insert/*Save/*Replace/*Update/Where/Data functions 2019-02-26 01:19:01 +08:00
49a1308875 ghttp.Session updates 2019-02-25 23:08:26 +08:00
4332580c01 disable build-in session variable in template parsing, when it's not necessary 2019-02-25 15:20:57 +08:00
72ecf2d2af TODO++ 2019-02-25 12:39:07 +08:00
0f854e46d8 refact Merge function for garray; add more frequently-used type alias for gf 2019-02-22 09:08:46 +08:00
4564f38e1a add PopRands/Rands functions for garray 2019-02-20 19:06:08 +08:00
7e06bf6705 Merge branch 'master' into qiangg_garray2 2019-02-20 16:28:24 +08:00
3dd8b6ad33 version updates 2019-02-20 16:24:44 +08:00
3e0a975a88 fix issue in gcron, allow special char '?' for day and week pattern 2019-02-20 16:24:05 +08:00
6aa1c5b1eb ghttp updates 2019-02-20 16:07:11 +08:00
d780cf64c2 garray updates 2019-02-20 14:18:11 +08:00
1fb5a8cd6f comments for glog update 2019-02-20 11:16:10 +08:00
8925460718 comment updates of gregex package 2019-02-19 11:19:23 +08:00
9797701881 remove Search/InArray functions from gstr package; update custom http status handling logics of web server 2019-02-18 16:12:59 +08:00
8a50b180c0 README updates 2019-02-18 09:25:05 +08:00
159190d187 version updates 2019-02-17 20:49:58 +08:00
989d543a1f fix issue with error response status code 200 of web server; ghttp request client updates 2019-02-17 20:45:35 +08:00
fcc3a1b2f6 README updates 2019-02-15 23:21:46 +08:00
cfdeb87093 gfile updates 2019-02-15 21:30:35 +08:00
74eef34ec2 gtest updates; TODO++ 2019-02-15 08:55:29 +08:00
2e87d5322f remove sarama-cluster 2019-02-14 19:30:58 +08:00
e89a49f39a remove gkafka 2019-02-14 19:24:45 +08:00
5d3fd91f0b remove sarama 2019-02-14 19:21:38 +08:00
f455d22893 move gkafka to new repo 2019-02-14 17:04:20 +08:00
c00f528098 remove reuseport in ghttp 2019-02-14 15:43:31 +08:00
6f02ad60eb hot fix issue in tpl auto update 2019-02-14 13:38:52 +08:00
ca6c0791ae fix issue with greuseport in windows 2019-02-03 21:41:32 +08:00
ee485dbfd8 README updates 2019-02-02 17:55:22 +08:00
2ecaa12647 travis updates 2019-02-02 16:54:04 +08:00
580e099cb7 update imports 2019-02-02 16:42:26 +08:00
83e50f0d38 update imports 2019-02-02 16:35:26 +08:00
48c770b475 update dependences 2019-02-02 16:31:34 +08:00
ba67101942 updates 2019-02-02 16:22:41 +08:00
72efde09b3 travis updates 2019-02-02 16:20:36 +08:00
76c49170bd change gitee.com/johng/gf -> github.com/gogf/gf 2019-02-02 16:18:25 +08:00
150aa7e2c2 comments update 2019-02-02 15:21:48 +08:00
0762fec696 comments update 2019-02-02 15:16:45 +08:00
1447496efa comments update 2019-02-02 14:22:32 +08:00
6fb9eafef0 garray updates 2019-02-02 09:07:40 +08:00
16bde5e9fc gmap updates 2019-02-01 22:25:58 +08:00
7b85c44444 add Clone for garray; comments of garray update 2019-02-01 22:00:58 +08:00
87d553fca2 greuseport updates 2019-02-01 21:05:56 +08:00
ba050d4c86 merge qiangg_reuseport 2019-02-01 20:51:02 +08:00
90cd7f49fd greuseport updates 2019-02-01 20:49:02 +08:00
29b42290c7 merge master 2019-02-01 20:46:53 +08:00
6c6c64bb3b rename category string to text 2019-02-01 20:45:11 +08:00
ae7db2cf9f gmap updates 2019-02-01 18:40:48 +08:00
1afab62dec fulfil unit test cases for garray 2019-02-01 18:33:53 +08:00
4171ca992e add reverse feature for function Sort of normal array; fulfil unit test cases for StringArray 2019-02-01 18:14:12 +08:00
e1166a8a80 Merge branch 'master' into develop 2019-02-01 17:55:06 +08:00
1dc6c799e1 version updates 2019-02-01 17:48:48 +08:00
ae1e075696 fix issue in binSearch of garray 2019-02-01 17:44:58 +08:00
74a7f71894 gset/gmap update 2019-02-01 17:30:23 +08:00
f2e149d3b3 fulfil unit test cases of gstr 2019-02-01 14:38:45 +08:00
92c0bf9cdc fix unit test cases of ghttp 2019-02-01 11:53:10 +08:00
020b050fb1 fix unit test cases for gstr/gconv/ghttp 2019-02-01 11:42:25 +08:00
08d71cede3 merge qiangg_php 2019-02-01 00:04:34 +08:00
be1f6cfbae Merge branch 'master' of https://gitee.com/johng/gf into develop 2019-02-01 00:02:17 +08:00
9aa5e139cf fulfil unit test cases of gstr 2019-02-01 00:02:01 +08:00
1d5c3d62dd fullfil unit test cases of gset 2019-01-31 21:15:39 +08:00
0e611ae94b merge master 2019-01-31 18:14:02 +08:00
908a46d27d gtest updates 2019-01-31 13:00:17 +08:00
85677b952f fulfil funcs of gstr/gset/garray; move gstr/gregex to the string category; move gtest to the test category 2019-01-30 21:27:03 +08:00
5eaa6183b5 update benchmark test cases of gregex 2019-01-28 12:03:47 +08:00
4036d40c58 add batchSqlResult for gform; TODO++ 2019-01-27 19:44:05 +08:00
0b80cbb0dc merge master 2019-01-27 19:18:24 +08:00
79cb386d82 reuseport updates 2019-01-27 16:40:16 +08:00
09be68831b reuseport updates 2019-01-27 14:25:31 +08:00
0ac13c2b81 reuseport updates 2019-01-27 14:14:59 +08:00
94ef38e3cc reuseport updates 2019-01-27 03:28:55 +08:00
76882ac01c update golang.org/x/sys, add reuseport package 2019-01-27 03:13:20 +08:00
59945fbe91 README updates 2019-01-25 12:47:53 +08:00
c4962ec017 gfpool updates 2019-01-24 20:15:11 +08:00
58d60bc899 add json tag support for struct converting of gconv 2019-01-24 15:37:53 +08:00
5dd0a78423 ghttp updates 2019-01-24 14:03:48 +08:00
141ea7cc2d ghttp updates 2019-01-24 13:50:26 +08:00
a488d1dbf7 ghttp updates 2019-01-24 13:40:48 +08:00
2493 changed files with 380877 additions and 247460 deletions

34
.gitee/ISSUE_TEMPLATE.MD Normal file
View File

@ -0,0 +1,34 @@
<!-- 为更高效率地交流并解决问题请按照以下模板提交issue感谢 -->
### 1. 您当前使用的`Go`版本,及系统版本、系统架构?
<!-- 使用 `go version` 命令查看,期望的结果如:`go 1.12, linux/amd64` -->
### 2. 您当前使用的`GoFrame`框架版本?
<!-- 框架版本可以查看自己项目下的 `go.mod`,或者框架文件 `version.go` -->
### 3. 更新到最新的框架版本是否能够解决问题?
<!-- 务必检查是否相同问题已在新版本中已修复 -->
### 4. 问题描述?
<!--
请您尽可能地提供一份最短的,可复现问题的代码。
代码尽可能地完整,最好是可以直接编译运行。
-->
### 5. 您期望得到的结果?
### 6. 您实际得到的结果?

36
.github/ISSUE_TEMPLATE.MD vendored Normal file
View File

@ -0,0 +1,36 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
### 1. What version of `Go` and system type/arch are you using?
<!--
Please paste the output of command `go version` from your terminal.
What expect to see is like: `go 1.12, linux/amd64`
-->
### 2. What version of `GoFrame` are you using?
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
### 3. Can this issue be reproduced with the latest release?
### 4. What did you do?
<!--
If possible, provide a copy of shortest codes for reproducing the error.
A complete runnable program is best.
-->
### 5. What did you expect to see?
### 6. What did you see instead?

1
.gitignore vendored
View File

@ -7,7 +7,6 @@
.settings/
.vscode/
vender/
log/
composer.lock
gitpush.sh
pkg/

View File

@ -1,7 +1,8 @@
language: go
go:
- "1.11.x"
- "1.11.x"
- "1.12.x"
branches:
only:
@ -9,25 +10,31 @@ branches:
- develop
env:
- GITEE_GF=$GOPATH/src/gitee.com/johng/gf GO111MODULE=on
- GO111MODULE=on
services:
- mysql
- redis-server
addons:
hosts:
- local
before_install:
- pwd
install:
- pwd
- mkdir -p $GITEE_GF
- cp * $GITEE_GF -R
- cd $GITEE_GF/g
- cat /etc/hosts
before_script:
- find . -name "*.go" | xargs gofmt -w
- git diff --exit-code
script:
- cd g
- GOARCH=386 go test -v ./...
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

25
DONATOR.MD Normal file
View File

@ -0,0 +1,25 @@
# Donators
| Name | Channel | Amount
|---|---|---
|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00
|[ireadx](https://github.com/ireadx)|alipay|¥201.00
|[mg91](https://gitee.com/mg91)|gitee|¥10.00
|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00
|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00
|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00
|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00
|[arden](https://github.com/arden)|alipay|¥10.00
|x*z|wechat|¥20.00
|潘兄|wechat|¥100.00
|Fly的狐狸|wechat|¥100.00
|土豆相公|alipay|¥66.60
|Hades|alipay|¥66.66
|蔡蔡|wechat|¥666.00
|上海金保证网络科技|bank|¥2000.00
<img src="https://goframe.org/images/donate.png"/>

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 john@johng.cn http://johng.cn
Copyright (c) 2017 john@goframe.org https://goframe.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,42 +1,41 @@
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
# GoFrame
<div align=center>
<img src="https://goframe.org/logo.png" width="100"/>
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://gfer.me)
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf/g#pkg-subdirectories)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/gogf/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/release/gogf/gf.svg?style=flat)](https://github.com/gogf/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.com/gogf/gf)
-->
</div>
`GF(GoFrame)` is a modular, 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, logger, template, https, hooks, rewrites and many more features.
`GF(GoFrame)` is a modular, lightweight, loosely coupled, high performance application development framework written in Go. Supporting graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing and many more features. Providing a series of core components and dozens of practical modules.
# Installation
```
go get -u gitee.com/johng/gf
go get -u github.com/gogf/gf
```
or use `go.mod`
or use `go.mod`:
```
require gitee.com/johng/gf latest
require github.com/gogf/gf latest
```
# Limitation
```
golang version >= 1.9.2
golang version >= 1.10
```
# Documentation
* [APIDoc](https://godoc.org/github.com/gogf/gf)
* [中文文档](https://goframe.org)
# Architecture
<div align=center>
<img src="https://gfer.me/images/arch.png"/>
<img src="https://goframe.org/images/arch.png"/>
</div>
# Quick Start
@ -45,8 +44,8 @@ golang version >= 1.9.2
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
@ -58,48 +57,25 @@ func main() {
}
```
[View More..](https://gfer.me/start/index)
[View More..](https://goframe.org/start/index)
# License
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
# Contributors(TOP 10)
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png?1530630243" width="60" align="left"></a>
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://gfer.me/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://gfer.me/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
<br /><br /><br />
# Donators
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
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)?
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
# Thanks
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>
<!--
# Sponsor
We appreciate any kind of sponsorship for `GF` development. If you've got some interested, please contact john@goframe.org.
-->

View File

@ -1,28 +1,23 @@
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
<div align=center>
<img src="https://goframe.org/logo.png" width="100"/>
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf/g#pkg-subdirectories)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://gfer.me)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/gogf/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/release/gogf/gf.svg?style=flat)](https://github.com/gogf/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.com/gogf/gf)
-->
</div>
`GF(Go Frame)`是一款模块化、松耦合、轻量级、高性能的Go应用开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等
并且提供了数十个内置核心开发模块集缓存、日志、时间、命令行、二进制、文件锁、内存锁、对象池、连接池、数据编码、进程管理、进程通信、文件监控、定时任务、TCP/UDP组件、
并发安全容器等等等等等等。
`GF(Go Frame)`是一款模块化、高性能、生产级Go应用开发框架。提供了常用的核心开发组件缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、
并发安全容器等等。并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎等等支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
# 特点
* 模块化、松耦合设计;
* 丰富实用的开发模块;
* 模块丰富,开箱即用
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 致力于项目的通用方案;
@ -31,21 +26,22 @@
# 安装
```html
go get -u gitee.com/johng/gf
go get -u github.com/gogf/gf
```
或者
`go.mod`
`go.mod`:
```
require gitee.com/johng/gf latest
require github.com/gogf/gf latest
```
# 限制
```shell
golang版本 >= 1.9.2
golang版本 >= 1.10
```
# 架构
<div align=center>
<img src="https://gfer.me/images/arch.png"/>
<img src="https://goframe.org/images/arch.png"/>
</div>
@ -61,8 +57,8 @@ golang版本 >= 1.9.2
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
@ -74,36 +70,17 @@ func main() {
}
```
[更多..](https://gfer.me/start/index)
[更多..](https://goframe.org/start/index)
# 协议
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
# 捐赠
# 贡献者(TOP 10)
如果您喜欢`GF`,要不[给开发者来杯咖啡吧](DONATOR.MD)
请在捐赠时备注您的`github`/`gitee`账号名称。
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png" width="60" align="left"></a>
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://gfer.me/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://gfer.me/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
<br /><br /><br />
# 捐赠者
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></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,146 @@
# `v1.7.0`
## 新功能/改进
1. 重构改进`glog`模块:
- 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效
- 增加日志内容的异步输出特性https://goframe.org/os/glog/async
- 增加日志输出内容的`Json`格式支持https://goframe.org/os/glog/json
- 增加`Flags`额外特性支持包括文件行号打印、自定义时间格式、异步输出等特性控制https://goframe.org/os/glog/flags
- 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成https://goframe.org/os/glog/writer
- 修改`SetStdPrint`方法名为`SetStdoutPrint`
- 修改链式方法`StdPrint`方法名为`Stdout`
- 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出
- 新增更多的链式方法支持https://goframe.org/os/glog/chain
1. 重构改进`gmap`模块:
- 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap`
- 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap`
- 改进`Map/Keys/Values`方法以提高性能
- 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes`
- 新增更多功能方法支持https://goframe.org/container/gmap/index
1. 改进`gtime`时间模块:
- 增加并完善更多的类`PHP`时间格式支持
- 新增更多功能方法,如`FormatTo`/`LayoutTo`等等
- 详见开发文档https://goframe.org/os/gtime/index
1. 改进`gdb`数据库模块:
- 增加对继承结构体的数据转换支持https://goframe.org/database/gdb/senior
- 新增`GetLastSql`方法用以在调试模式下获取最近一条执行的SQL语句
- 其他的细节处理改进
1. 改进`gtcp`通信模块:
- 完善处理细节,提高通信性能;
- 增加`TLS`服务端/客户端通信支持https://goframe.org/net/gtcp/tls
- 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题https://goframe.org/net/gtcp/conn/pkg
- TCP服务端增加`Close`方法
- 更多细节查看开发文档https://goframe.org/net/gtcp/index
1. 改进`gconv`类型转换模块
- 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration`
- 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换
- 详见开发文档https://goframe.org/util/gconv/struct
1. 改进`ghttp`模块:
- 日志输出增加`http/https`字段https://goframe.org/net/ghttp/logs
- 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性
- 增加`ghttp.Request.GetUrl`方法用以获取当前完整的URL请求地址
- `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`https://goframe.org/net/ghttp/client
1. 新增`gtree`树形数据结构容器支持https://goframe.org/container/gtree/index
1. 改进`gudp`通信模块具体请参考开发文档https://goframe.org/net/gudp/index
1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持https://goframe.org/os/gcfg/index
1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换https://goframe.org/database/gredis/index
1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes`
1. 改进`gjson`/`gparser`模块增加更多方法https://goframe.org/encoding/gjson/index
1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境;
1. 改进`grpool`协程池模块提高执行性能https://goframe.org/os/grpool/index
1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理
1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法
1. 去除`gvar.VarRead`接口类型支持
## Bug Fix
1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突;
1. 修复`gcron.AddOnce`方法的细节逻辑问题;
1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误;
1. 修复`gview`模板引擎的并发安全问题;
1. 修复`ghttp.Server`的SESSION初始化过期时间问题
# `v1.6.0` (2019-04-09)
## 新功能/改进
1. `gcron`定时任务模块增加运行日志记录功能https://goframe.org/os/gcron/index
1. `gredis`增加全局分组配置功能,并增加更多的配置选项`maxIdle/maxActive/idleTimeout/maxConnLifetime`https://goframe.org/database/gredis/index
1. `gcfg`模块增加更多的默认配置文件检索路径,并且增加全局分组配置特性,增加`Instance`单例方法https://goframe.org/os/gcfg/index
1. `gview`模块增加更多的默认配置文件检索路径,并且增加`Instance`单例方法https://goframe.org/os/gview/index
1. `ghttp`模块新功能及改进:
- 新增`CORS`HTTP(S)跨域请求特性: https://goframe.org/net/ghttp/cors
- 增加`TLSConfig`配置功能;
- 去掉路由注册方法的`error`返回值,当产生注册错误时直接终端打印错误/输出到日志文件;
- 增加在`HTTP Code 302`跳转时的`Set-Cookie`支持;
- 增加对`SESSION ID`的安全性检查;
- 增加对基于`HTTPS`的`WebSocket`支持(`WSS`https://goframe.org/net/ghttp/websocket/index
- `Request`对象增加`Error`方法,用于输出自定义错误信息到`WebServer`错误日志中;
- 其他一些改进;
1. `gdb`模块新功能及改进:
- 新增`Instance`单例管理方法;
- 新增`Structs/Scan`链式操作方法,`gdb.DB/TX`新增`GetStructs/GetScan`方法,用于结果集`struct`/`slice`映射转换https://goframe.org/database/gdb/chaining
- 新增`Safe`链式操作方法默认非并发安全用于链式安全控制https://goframe.org/database/gdb/chaining
- `Where`链式操作方法改进:
- 方法支持任意的`string/map/slice/struct/*struct`类型;
- 逻辑调整,当链式操作中存在多个`Where`方法调用时,自动转换为`And`条件;
- 支持`slice`条件参数,常用在`SELECT IN`查询中,例如:`Where("uid IN(?)", g.Slice{1,2,3})`
- 支持在`map`类型条件参数的`key`中传递条件,例如:`Where(g.Map{"uid>?", uid})`
1. `gconv`及`gvalid`模块改进并去掉对私有`struct`方法属性的转换/校验;
1. `gconv.Map`转换方法新增对`json tag`: `-`, `omitempty`的支持: https://goframe.org/util/gconv/map
1. `gstr`模块新增 `ReplaceI/ReplaceIByArray/ReplaceIByMap`大小写非敏感替换方法;
1. `gutil`模块增加`IsEmpty`方法用于判断给定变量是否为空整型0, 布尔false, slice/map长度为0, 其他为nil的情况判断为空并增加快捷方法`g.IsEmpty`
1. `gutil`模块增加`Export`方法,用于导出返回格式化打印的变量内容字符串,并增加快捷方法`g.Export`
1. `gspath`增加缓存及非缓存检索检索方法`Search`/`SearchWithCache`
1. `gjson`模块增加默认的`UseNumber`功能支持;
1. `gmap`增加`SetIfNotExistFunc/SetIfNotExistFuncLock`方法;
1. 迁移`greuseport`模块到新的仓库https://github.com/gogf/greuseport
1. 大量的单元测试完善;
## Bug Fix
1. 修复`gqueue`模块的资源竞争问题;
1. 修复`gconv.GTime`转换失败问题;
1. 修复`gconv.String`在转换`int`参数时字节溢出问题;
1. 修复`ghttp.Request`的`HTTP Basic Auth`校验问题;
1. 修复`gxml`针对于非`UTF-8`编码内容转换的并发安全问题;
1. 修复`gtime`部分`Format``G`&`j`)格式失效问题;
1. 修复`gudp.Conn`对象的`RemoteAddr`获取客户端连接地址方法问题;
1. 修复`gmap/gcache`模块的`GetOrSetFuncLock`方法,增加对回调方法返回值的`nil`判断只有非nil返回值才会被保存
# `v1.5.8` (2019-02-28)
## 新特性
1. 主库从`gitee`迁移到了`github`( https://github.com/gogf/gf )`gitee`作为镜像站用于国内的代码贡献及ISSUE提交迁移说明详见https://goframe.org/upgradeto150
1. 对常用的`container`数组模块: `garray`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/garray
1. 对常用的`container`集合模块: `gset`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/gset
1. 对常用的`container`MAP模块: `gmap`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/gmap
1. 对常用的字符串模块: `gstr`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/text/gstr
1. 改进`gform`中对`struct`/`*struct`参数的支持,`*Insert/*Save/*Replace/*Update/Where/Data`方法的参数调整为`interface{}`类型,并支持任意类型的: `string/map/slice/struct/*struct`参数传递具体请参考https://goframe.org/database/orm/chaining
1. 新增/完善若干模块的单元测试用例, 包括:`gvalid`/`gregex`/`garray`/`gset`/`gmap`/`gstr`/`gconv`/`ghttp`/`gdb`
1. 由于`gkafka`模块比较重且不是框架核心模块因此将该模块迁移到新的仓库中独立管理并去掉相关依赖包https://github.com/gogf/gkafka
1. 新增`greuseport`模块用以实现TCP的`REUSEPORT`特性https://godoc.org/github.com/gogf/gf/g/net/greuseport
## 新功能/改进
1. 去掉模板引擎内置变量中自动初始化`session`对象带来的内存占用问题;
1. `ghttp.Client`改进增加若干方法详见https://goframe.org/net/ghttp/client
1. `ghttp`分组路由增加`COMMON`方法,用以注册常用的`HTTP METHOD`(`GET/PUT/POST/DELETE`)路由;
1. 更新框架依赖的`golang.org/x/sys`模块;
1. 改进`gform`的批量操作(`Batch*`操作)返回结果对象,可以通过该结果对象获得批量操作准确的受影响记录行数;
1. 将`gstr`/`gregex`模块从`util`分类迁移到了`text`分类目录下;
1. 将`gtest`模块从`util`分类迁移到了`test`分类目录下;
1. 完善`glog`方法注释;
## Bug Fix
1. 修复带点的邮件格式,用`gvalid.Check`的"`email`"规则不能匹配成功;
1. 修复`gvalid.Check`在`regex`规则下的检查失败问题;
1. 修复`gcron`模块定时规则中天和周不允许`?`符号的问题;
1. 修复`ghttp.Server`在部分异常情况下仍然返回`200`状态码的问题;
1. 修复`gfpool`模块中由于原子操作问题造成的高并发"内存泄露"问题;
1. 修复分组路由注册对象/控制时,方法`Index`的路由仅能通过`/xxx/index`访问的问题;
1. 修复模板引擎使用中,当不存在`config.toml`(即使没使用)配置文件时的报错问题;
1. 其他一些修复;
# `v1.4.6` (2019-01-24)
## 新特性
@ -31,20 +174,20 @@
# `v1.3.8` (2018-12-26)
## 新特性
1. 对`gform`完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例([https://gfer.me/database/orm/index](https://gfer.me/database/orm/index))
1. `WebServer`路由注册新增分组路由特性([https://gfer.me/net/ghttp/group](https://gfer.me/net/ghttp/group));
1. `WebServer`新增`Rewrite`路由重写特性([https://gfer.me/net/ghttp/static](https://gfer.me/net/ghttp/static));
1. 对`gform`完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例([https://goframe.org/database/orm/index](https://goframe.org/database/orm/index))
1. `WebServer`路由注册新增分组路由特性([https://goframe.org/net/ghttp/group](https://goframe.org/net/ghttp/group));
1. `WebServer`新增`Rewrite`路由重写特性([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static));
1. 增加框架运行时对开发环境的自动识别;
1. 增加了`Travis CI`自动化构建/测试;
## 新功能
1. 改进`WebServer`静态文件服务功能,增加`SetStaticPath`/`AddStaticPath`方法([https://gfer.me/net/ghttp/static](https://gfer.me/net/ghttp/static))
1. `gform`新增`Filter`链式操作方法,用于过滤参数中的非表字段键值对([https://gfer.me/database/orm/linkop](https://gfer.me/database/orm/linkop)
1. 改进`WebServer`静态文件服务功能,增加`SetStaticPath`/`AddStaticPath`方法([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static))
1. `gform`新增`Filter`链式操作方法,用于过滤参数中的非表字段键值对([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop)
1. `gcache`新增`Data`方法,用以获取所有的缓存数据项;
1. `gredis`增加`GetConn`方法获取原生redis连接对象
## 功能改进
1. 改进`gform`的`Where`方法,支持`slice`类型的参数,并更方便地支持`in`操作查询([https://gfer.me/database/orm/linkop](https://gfer.me/database/orm/linkop)
1. 改进`gform`的`Where`方法,支持`slice`类型的参数,并更方便地支持`in`操作查询([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop)
1. 改进`gproc`进程间通信数据结构,将`pid`字段从`16bit`扩展为`24bit`
1. 改进`gconv`/`gmap`/`garray`,增加若干操作方法;
1. 改进`gview`模板引擎中的`date`内置函数,当给定的时间戳为空时打印当前的系统时间;
@ -67,20 +210,20 @@
# `v1.2.11` (2018-11-26)
## 新特性
1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://gfer.me/database/orm/database](https://gfer.me/database/orm/database))
1. 完成`gvalid`模块校验结果的顺序特性([https://gfer.me/util/gvalid/checkmap](https://gfer.me/util/gvalid/checkmap));
1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://gfer.me/net/ghttp/service/object](https://gfer.me/net/ghttp/service/object))
1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://gfer.me/os/gview/funcs](https://gfer.me/os/gview/funcs));
1. 模板引擎新增内置变量`Config` ([https://gfer.me/os/gview/vars](https://gfer.me/os/gview/vars));
1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://goframe.org/database/orm/database](https://goframe.org/database/orm/database))
1. 完成`gvalid`模块校验结果的顺序特性([https://goframe.org/util/gvalid/checkmap](https://goframe.org/util/gvalid/checkmap));
1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object))
1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://goframe.org/os/gview/funcs](https://goframe.org/os/gview/funcs));
1. 模板引擎新增内置变量`Config` ([https://goframe.org/os/gview/vars](https://goframe.org/os/gview/vars));
1. 改进`gconv.Struct`转换默认规则,支持不区分大小写的键名与属性名称匹配;
1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://gfer.me/database/orm/config](https://gfer.me/database/orm/config))
1. `gfsnotify`模块增加对特定回调的取消注册功能([https://gfer.me/os/gfsnotify/index](https://gfer.me/os/gfsnotify/index)
1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://goframe.org/database/orm/config](https://goframe.org/database/orm/config))
1. `gfsnotify`模块增加对特定回调的取消注册功能([https://goframe.org/os/gfsnotify/index](https://goframe.org/os/gfsnotify/index)
## 新功能
1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://gfer.me/net/ghttp/request](https://gfer.me/net/ghttp/request);
1. 改进`ghttp.Response`,增加`ServeFileDownload`方法用于WebServer引导客户端下载文件([https://gfer.me/net/ghttp/response](https://gfer.me/net/ghttp/response));
1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://goframe.org/net/ghttp/request](https://goframe.org/net/ghttp/request);
1. 改进`ghttp.Response`,增加`ServeFileDownload`方法用于WebServer引导客户端下载文件([https://goframe.org/net/ghttp/response](https://goframe.org/net/ghttp/response));
1. `gvar`模块新增`gvar.VarRead`只读接口,用于控制对外只暴露数据读取功能;
1. 增加`g.Throw`抛异常方法,`g.TryCatch`异常捕获方法封装;
1. 改进`gcron`模块增加自定义的Cron管理对象增加`New/Start/Stop`方法;
@ -88,9 +231,9 @@
## 功能改进
1. WebServer添加`RouterCacheExpire`配置参数,用于设置路由检索缓存过期时间;
1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://gfer.me/net/ghttp/service/hook](https://gfer.me/net/ghttp/service/hook));
1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://goframe.org/net/ghttp/service/hook](https://goframe.org/net/ghttp/service/hook));
1. 当前工作目录为系统临时目录时,`gcfg`/`gview`/`ghttp`模块默认不添加工作目录到搜索路径;
1. 改进`WebSocket`默认支持跨域请求([https://gfer.me/net/ghttp/websocket](https://gfer.me/net/ghttp/websocket));
1. 改进`WebSocket`默认支持跨域请求([https://goframe.org/net/ghttp/websocket](https://goframe.org/net/ghttp/websocket));
1. 改进`gtime.Format`支持中文;
1. 改进`gfsnotify`,支持编辑器对文件非执行标准编辑时(RENAME+CHMOD)的热更新问题;
1. 改进`gtype.Set`方法增加Set原子操作返回旧的变量值;
@ -99,12 +242,12 @@
1. `gstr`模块增加对中文截取方法;
1. 改进`gtime.StrToTime`对常用时间格式匹配模式,新增`gtime.ParseTimeFromContent`方法;
1. 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式;
1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://gfer.me/util/grand/index](https://gfer.me/util/grand/index));
1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://goframe.org/util/grand/index](https://goframe.org/util/grand/index));
## 问题修复
1. 修复`gspath`模块在`windows`下搜索失效问题;
1. 修复`gspath`模块Search时带有indexFiles的检索问题;
1. bug fix INZS1([https://gitee.com/johng/gf/issues/INZS1](https://gitee.com/johng/gf/issues/INZS1));
1. bug fix INZS1([https://github.com/gogf/gf/issues/INZS1](https://github.com/gogf/gf/issues/INZS1));
1. 修复`gproc.ShellRun`在windows下的执行问题;
@ -343,8 +486,8 @@
12、gdb数据库ORM包增加And/Or条件链式方法并改进Where/Data方法参数灵活性
13、对于新增加的模块同时也增加了对应的开发文档并梳理完善了现有的其他模块开发文档
14、修复ISSUE:
#IISWI gitee.com/johng/gf/issues/IISWI,
#IISMY gitee.com/johng/gf/issues/IISMY,
#IISWI github.com/gogf/gf/issues/IISWI,
#IISMY github.com/gogf/gf/issues/IISMY,
反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48)

47
TODO.MD
View File

@ -1,23 +1,15 @@
# ON THE WAY
1. 增加图形验证码支持,至少支持数字和英文字母;
1. 增加热编译工具,提高开发环境的开发/测试效率媲美PHP开发效率
1. 增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射
1. ghttp.Response增加输出内容后自动退出当前请求机制不需要用户手动return参考beego如何实现
1. Cookie&Session数据池化处理
1. ghttp.Client增加proxy特性
1. gtime增加对时区转换的封装并简化失去转换时对类似+80500时区的支持
1. orm增加sqlite对Save方法的支持(去掉触发器语句);
1. ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps)
1. ghttp路由功能增加分组路由特性
1. ghttp增加返回数据压缩机制
1. gview中的template标签失效问题
1. gfile文件stat信息使用gfsnotify进行缓存更新改进
1. ghttp.Server增加proxy功能特性本地proxy和远程proxy本地即将路由规则映射远程即反向代理
1. gjson对大json数据的解析效率问题
1. ghttp增加route name特性并同时支持backend和template(提供内置函数)引用可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上
1. ghttp.Client自动Close机制
1. gvalid校验支持当第一个规则失败后便不再校验后续的规则最好做成链式操作
1. 检查ghttp.Server超时问题
1. gvalid增加支持对[]rune的长度校验(一个中文占3个字节)
1. ghttp.Request增加对输入参数的自动HtmlEncode机制
1. 常量命名风格根据golint进行修改
@ -36,17 +28,28 @@
- https://github.com/Masterminds/sprig
1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
1. gtcp提供简便的包发送/接收方法(SendPkg/RecvPkg)以解决常见的TCP通信粘包问题并完善文档参考https://www.cnblogs.com/kex1n/p/6502002.html
1. gfile对于文件的读写强行使用了gfpool在某些场景下不合适需要考虑剥离开并为开发者提供单独的指针池文件操作特性
1. 路由增加不区分大小写得匹配方式;
1. str_ireplace: http://php.net/manual/en/function.str-ireplace.php
1. strpos/stripos/strrpos/strripos: http://php.net/manual/en/function.stripos.php
1. 改进WebServer获取POST参数处理逻辑当提交非form数据时例如json数据针对某些方法可以直接解析
1. WebServer增加可选择的路由覆盖配置默认情况下不覆盖
1. gkafka这个包比较重未来从框架中剥离出来
1. grpool性能压测结果变慢的问题
1. 增加jumplist的数据结构容器
1. DelayQueue/PriorityQueue
1. gconv针对struct的转换增加json tag支持gconv.Map默认也支持json tag;
1. 权限管理模块;
1. 从ghttp中剥离SESSION功能构成单独的模块gsession
1. 改进gproc进程间通信处理逻辑提高稳定性以应对进程间大批量的数据发送/接收;
1. ghttp的热重启的本地进程端口监听在不使用该特性时默认关闭掉
1. gtcp增加对TLS加密通信的支持
1. 添加Save/Replace/BatchSave/BatchReplace方法对sqlite数据库的支持
1. 添加sqlite数据库的单元测试用例
1. gredis增加cluster支持
1. gset.Add/Remove/Contains方法增加批量操作支持
1. gmlock增加手动清理机制当内存锁不再使用时由调用端决定是否清理内存锁
1. gtimer增加DelayAdd*方法返回Entry对象以便DelayAdd*的定时任务也能进行状态控制gcron同理需要改进
1. 改进gdb对pgsql/mssql/oracle的支持使用方法覆盖的方式改进操作而不是完全依靠正则替换的方式
1. gdb的Cache缓存功能增加可自定义缓存接口以便支持外部缓存功能缓存接口可以通过io.ReadWriter接口实现
1. grpool增加支持阻塞添加任务接口
1. gdb.Model在链式安全的对象创建中增加sync.Pool的使用
1. 增加g.Table快捷方法以方便操作数据表但是得考虑后续模型操作设计特别是脚手架的模型管理
# DONE
1. gconv完善针对不同类型的判断例如尽量减少sprintf("%v", xxx)来执行string类型的转换
@ -105,4 +108,20 @@
1. gform对于MySQL字段类型为datetime类型的时区问题分析
1. 改进证书打开失败时的WebServer错误提示前置HOOK校验后关闭后续的HOOK逻辑执行
1. 目前WebServer的HOOK是按照优先级执行的需要增加覆盖特性
1. 更新跨域请求CORS相关功能文档
1. ghttp.Response增加输出内容后自动退出当前请求机制不需要用户手动return参考beego如何实现
1. gcfg包目前允许添加重复的目录路径需要在SetPath/AddPath时判断重复性不能添加重复的路径
1. gdb执行数据写入时如果参数为struct/[]struct自动映射与表字段对应关系不再使用gconv标签标识
1. gdb的Data方法支持struct参数传入
1. gfcache依旧使用gcache作为缓存控制对象不要使用gmap
1. 增加对ghttp路由注册的{.struct}/{.method}单元测试;
1. gconv针对struct的转换增加json tag支持gconv.Map默认也支持json tag, 完善开发文档;
1. 增加SO_REUSEPORT的支持
1. gkafka这个包比较重未来从框架中剥离出来
1. str_ireplace: http://php.net/manual/en/function.str-ireplace.php
1. strpos/stripos/strrpos/strripos: http://php.net/manual/en/function.stripos.php
1. gfile对于文件的读写强行使用了gfpool在某些场景下不合适需要考虑剥离开并为开发者提供单独的指针池文件操作特性
1. ghttp.Client自动Close机制
1. ghttp路由功能增加分组路由特性
1. 增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射
1. gview中的template标签失效问题

View File

@ -1,7 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package container

View File

@ -1,14 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// Package garray provides kinds of concurrent-safe(alternative) arrays.
//
// 并发安全的数组.
// Package garray provides concurrent-safe/unsafe arrays.
package garray
func New(size int, cap int, unsafe...bool) *Array {
return NewArray(size, cap, unsafe...)
}

View File

@ -0,0 +1,19 @@
// 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 this file,
// You can obtain one at https://github.com/gogf/gf.
package garray
type apiSliceInterface interface {
Slice() []interface{}
}
type apiSliceInt interface {
Slice() []int
}
type apiSliceString interface {
Slice() []string
}

View File

@ -1,176 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package garray
import (
"gitee.com/johng/gf/g/internal/rwmutex"
)
type IntArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
size int // 初始化设置的数组大小
array []int // 底层数组
}
func NewIntArray(size int, cap int, unsafe...bool) *IntArray {
a := &IntArray{
mu : rwmutex.New(unsafe...),
}
a.size = size
if cap > 0 {
a.cap = cap
a.array = make([]int, size, cap)
} else {
a.array = make([]int, size)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
func (a *IntArray) Get(index int) int {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 设置指定索引的数据项, 调用方注意判断数组边界
func (a *IntArray) Set(index int, value int) {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *IntArray) InsertBefore(index int, value int) {
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...)
}
// 在当前索引位置后插入一个数据项, 调用方注意判断数组边界
func (a *IntArray) InsertAfter(index int, value int) {
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...)
}
// 删除指定索引的数据项, 调用方注意判断数组边界
func (a *IntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 追加数据项
func (a *IntArray) Append(value...int) {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
}
// 数组长度
func (a *IntArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
func (a *IntArray) Slice() []int {
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]int, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
}
// 清空数据数组
func (a *IntArray) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]int, a.size, a.cap)
} else {
a.array = make([]int, a.size)
}
}
a.mu.Unlock()
}
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
func (a *IntArray) Search(value int) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// 清理数组中重复的元素项
func (a *IntArray) Unique() *IntArray {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *IntArray) LockFunc(f func(array []int)) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
}
// 使用自定义方法执行加锁读取操作
func (a *IntArray) RLockFunc(f func(array []int)) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}

View File

@ -1,194 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package garray
import (
"gitee.com/johng/gf/g/internal/rwmutex"
)
type Array struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
size int // 初始化设置的数组大小
array []interface{} // 底层数组
}
func NewArray(size int, cap int, unsafe...bool) *Array {
a := &Array{
mu : rwmutex.New(unsafe...),
}
a.size = size
if cap > 0 {
a.cap = cap
a.array = make([]interface{}, size, cap)
} else {
a.array = make([]interface{}, size)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
func (a *Array) Get(index int) interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 设置指定索引的数据项, 调用方注意判断数组边界
func (a *Array) Set(index int, value interface{}) {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *Array) InsertBefore(index int, value interface{}) {
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...)
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *Array) InsertAfter(index int, value interface{}) {
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...)
}
// 删除指定索引的数据项, 调用方注意判断数组边界
func (a *Array) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
func (a *Array) PopLeft() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
func (a *Array) PopRight() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
}
// 追加数据项
func (a *Array) Append(value...interface{}) {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
}
// 数组长度
func (a *Array) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
func (a *Array) Slice() []interface{} {
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]interface{}, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
}
// 清空数据数组
func (a *Array) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]interface{}, a.size, a.cap)
} else {
a.array = make([]interface{}, a.size)
}
}
a.mu.Unlock()
}
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
func (a *Array) Search(value interface{}) int {
if len(a.array) == 0 {
return -1
}
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) Unique() *Array {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *Array) LockFunc(f func(array []interface{})) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
}
// 使用自定义方法执行加锁读取操作
func (a *Array) RLockFunc(f func(array []interface{})) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}

View File

@ -0,0 +1,618 @@
// 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 garray
import (
"bytes"
"fmt"
"math"
"sort"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
)
type IntArray struct {
mu *rwmutex.RWMutex
array []int
}
// NewIntArray creates and returns an empty array.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewIntArray(unsafe ...bool) *IntArray {
return NewIntArraySize(0, 0, unsafe...)
}
// NewIntArraySize create and returns an array with given size and cap.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewIntArraySize(size int, cap int, unsafe ...bool) *IntArray {
return &IntArray{
mu: rwmutex.New(unsafe...),
array: make([]int, size, cap),
}
}
// NewIntArrayFrom creates and returns an array with given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewIntArrayFrom(array []int, unsafe ...bool) *IntArray {
return &IntArray{
mu: rwmutex.New(unsafe...),
array: array,
}
}
// NewIntArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewIntArrayFromCopy(array []int, unsafe ...bool) *IntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &IntArray{
mu: rwmutex.New(unsafe...),
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 {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set sets value to specified index.
func (a *IntArray) Set(index int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// SetArray sets the underlying slice array with the given <array>.
func (a *IntArray) SetArray(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
func (a *IntArray) Replace(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
return a
}
// Sum returns the sum of values in an array.
func (a *IntArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += v
}
return
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// in increasing order(default) or decreasing order
func (a *IntArray) Sort(reverse ...bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if len(reverse) > 0 && reverse[0] {
sort.Slice(a.array, func(i, j int) bool {
if a.array[i] < a.array[j] {
return false
}
return true
})
} else {
sort.Ints(a.array)
}
return a
}
// SortFunc sorts the array by custom function <less>.
func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
return a
}
// 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.
func (a *IntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *IntArray) PushLeft(value ...int) *IntArray {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *IntArray) PushRight(value ...int) *IntArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// PopLeft pops and returns an item from the beginning of array.
func (a *IntArray) PopLeft() int {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1:]
return value
}
// PopRight pops and returns an item from the end of array.
func (a *IntArray) PopRight() int {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[:index]
return value
}
// PopRand randomly pops and return an item out of array.
func (a *IntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *IntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
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:]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *IntArray) PopLefts(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0:size]
a.array = a.array[size:]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *IntArray) PopRights(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index:]
a.array = a.array[:index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *IntArray) Range(start int, end ...int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]int)(nil)
if a.mu.IsSafe() {
array = make([]int, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *IntArray) SubSlice(offset int, length ...int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// See PushRight.
func (a *IntArray) Append(value ...int) *IntArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Len returns the length of array.
func (a *IntArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *IntArray) Slice() []int {
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]int, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// Clone returns a new array, which is a copy of current array.
func (a *IntArray) Clone() (newArray *IntArray) {
a.mu.RLock()
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewIntArrayFrom(array, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *IntArray) Clear() *IntArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]int, 0)
}
a.mu.Unlock()
return a
}
// Contains checks whether a value exists in the array.
func (a *IntArray) Contains(value int) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *IntArray) Search(value int) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// Unique uniques the array, clear repeated items.
func (a *IntArray) Unique() *IntArray {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[:j], a.array[j+1:]...)
}
}
}
a.mu.Unlock()
return a
}
// LockFunc locks writing by callback function <f>.
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// 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 *StringArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedStringArray:
a.Append(gconv.Ints(v.Slice())...)
default:
a.Append(gconv.Ints(array)...)
}
return a
}
// 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 {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *IntArray) Chunk(size int) [][]int {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]int
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
}
// Pad pads array to the specified length with <value>.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// then no padding takes place.
func (a *IntArray) Pad(size int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]int, n)
for i := 0; i < n; i++ {
tmp[i] = value
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
return a
}
// Rand randomly returns one item from array(no deleting).
func (a *IntArray) Rand() int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// 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)
}
n := make([]int, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
}
return n
}
// Shuffle randomly shuffles the array.
func (a *IntArray) Shuffle() *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
return a
}
// Reverse makes array with elements in reverse order.
func (a *IntArray) Reverse() *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
return a
}
// Join joins array elements with a string <glue>.
func (a *IntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *IntArray) CountValues() map[int]int {
m := make(map[int]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *IntArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -0,0 +1,612 @@
// 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 garray
import (
"bytes"
"fmt"
"math"
"sort"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
)
type Array struct {
mu *rwmutex.RWMutex
array []interface{}
}
// New creates and returns an empty array.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func New(unsafe ...bool) *Array {
return NewArraySize(0, 0, unsafe...)
}
// See New.
func NewArray(unsafe ...bool) *Array {
return NewArraySize(0, 0, unsafe...)
}
// NewArraySize create and returns an array with given size and cap.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewArraySize(size int, cap int, unsafe ...bool) *Array {
return &Array{
mu: rwmutex.New(unsafe...),
array: make([]interface{}, size, cap),
}
}
// See NewArrayFrom.
func NewFrom(array []interface{}, unsafe ...bool) *Array {
return NewArrayFrom(array, unsafe...)
}
// See NewArrayFromCopy.
func NewFromCopy(array []interface{}, unsafe ...bool) *Array {
return NewArrayFromCopy(array, unsafe...)
}
// NewArrayFrom creates and returns an array with given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewArrayFrom(array []interface{}, unsafe ...bool) *Array {
return &Array{
mu: rwmutex.New(unsafe...),
array: array,
}
}
// NewArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewArrayFromCopy(array []interface{}, unsafe ...bool) *Array {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &Array{
mu: rwmutex.New(unsafe...),
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{} {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set sets value to specified index.
func (a *Array) Set(index int, value interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// SetArray sets the underlying slice array with the given <array>.
func (a *Array) SetArray(array []interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
func (a *Array) Replace(array []interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
return a
}
// Sum returns the sum of values in an array.
func (a *Array) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// SortFunc sorts the array by custom function <less>.
func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
return a
}
// 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{} {
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency。
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *Array) PushLeft(value ...interface{}) *Array {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *Array) PushRight(value ...interface{}) *Array {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// PopRand randomly pops and return an item out of array.
func (a *Array) PopRand() interface{} {
return a.Remove(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)
}
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:]...)
}
return array
}
// PopLeft pops and returns an item from the beginning of array.
func (a *Array) PopLeft() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1:]
return value
}
// PopRight pops and returns an item from the end of array.
func (a *Array) PopRight() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[:index]
return value
}
// 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 size > length {
size = length
}
value := a.array[0:size]
a.array = a.array[size:]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *Array) PopRights(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index:]
a.array = a.array[:index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *Array) Range(start int, end ...int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]interface{})(nil)
if a.mu.IsSafe() {
array = make([]interface{}, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *Array) SubSlice(offset int, length ...int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]interface{}, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// See PushRight.
func (a *Array) Append(value ...interface{}) *Array {
a.PushRight(value...)
return a
}
// Len returns the length of array.
func (a *Array) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *Array) Slice() []interface{} {
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]interface{}, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// Clone returns a new array, which is a copy of current array.
func (a *Array) Clone() (newArray *Array) {
a.mu.RLock()
array := make([]interface{}, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewArrayFrom(array, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *Array) Clear() *Array {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]interface{}, 0)
}
a.mu.Unlock()
return a
}
// Contains checks whether a value exists in the array.
func (a *Array) Contains(value interface{}) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *Array) Search(value interface{}) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// Unique uniques the array, clear repeated items.
func (a *Array) Unique() *Array {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[:j], a.array[j+1:]...)
}
}
}
a.mu.Unlock()
return a
}
// LockFunc locks writing by callback function <f>.
func (a *Array) LockFunc(f func(array []interface{})) *Array {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *Array) RLockFunc(f func(array []interface{})) *Array {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// 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 *StringArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedStringArray:
a.Append(gconv.Interfaces(v.Slice())...)
default:
a.Append(gconv.Interfaces(array)...)
}
return a
}
// 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 {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *Array) Chunk(size int) [][]interface{} {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]interface{}
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
}
// Pad pads array to the specified length with <value>.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// then no padding takes place.
func (a *Array) Pad(size int, val interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]interface{}, n)
for i := 0; i < n; i++ {
tmp[i] = val
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
return a
}
// Rand randomly returns one item from array(no deleting).
func (a *Array) Rand() interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// 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)
}
n := make([]interface{}, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
}
return n
}
// Shuffle randomly shuffles the array.
func (a *Array) Shuffle() *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
return a
}
// Reverse makes array with elements in reverse order.
func (a *Array) Reverse() *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
return a
}
// Join joins array elements with a string <glue>.
func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *Array) CountValues() map[interface{}]int {
m := make(map[interface{}]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *Array) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -0,0 +1,618 @@
// 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 garray
import (
"bytes"
"fmt"
"math"
"sort"
"strings"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
)
type StringArray struct {
mu *rwmutex.RWMutex
array []string
}
// NewStringArray creates and returns an empty array.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewStringArray(unsafe ...bool) *StringArray {
return NewStringArraySize(0, 0, unsafe...)
}
// NewStringArraySize create and returns an array with given size and cap.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewStringArraySize(size int, cap int, unsafe ...bool) *StringArray {
return &StringArray{
mu: rwmutex.New(unsafe...),
array: make([]string, size, cap),
}
}
// NewStringArrayFrom creates and returns an array with given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewStringArrayFrom(array []string, unsafe ...bool) *StringArray {
return &StringArray{
mu: rwmutex.New(unsafe...),
array: array,
}
}
// NewStringArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewStringArrayFromCopy(array []string, unsafe ...bool) *StringArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &StringArray{
mu: rwmutex.New(unsafe...),
array: newArray,
}
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *StringArray) Get(index int) string {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set sets value to specified index.
func (a *StringArray) Set(index int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// SetArray sets the underlying slice array with the given <array>.
func (a *StringArray) SetArray(array []string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
func (a *StringArray) Replace(array []string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
return a
}
// Sum returns the sum of values in an array.
func (a *StringArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// in increasing order(default) or decreasing order
func (a *StringArray) Sort(reverse ...bool) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if len(reverse) > 0 && reverse[0] {
sort.Slice(a.array, func(i, j int) bool {
if strings.Compare(a.array[i], a.array[j]) < 0 {
return false
}
return true
})
} else {
sort.Strings(a.array)
}
return a
}
// SortFunc sorts the array by custom function <less>.
func (a *StringArray) SortFunc(less func(v1, v2 string) bool) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
return a
}
// InsertBefore inserts the <value> to the front of <index>.
func (a *StringArray) InsertBefore(index int, value string) *StringArray {
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 *StringArray) InsertAfter(index int, value string) *StringArray {
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.
func (a *StringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency。
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *StringArray) PushLeft(value ...string) *StringArray {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *StringArray) PushRight(value ...string) *StringArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// PopLeft pops and returns an item from the beginning of array.
func (a *StringArray) PopLeft() string {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1:]
return value
}
// PopRight pops and returns an item from the end of array.
func (a *StringArray) PopRight() string {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[:index]
return value
}
// PopRand randomly pops and return an item out of array.
func (a *StringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *StringArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
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:]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *StringArray) PopLefts(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0:size]
a.array = a.array[size:]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *StringArray) PopRights(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index:]
a.array = a.array[:index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *StringArray) Range(start int, end ...int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]string)(nil)
if a.mu.IsSafe() {
array = make([]string, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *StringArray) SubSlice(offset int, length ...int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// See PushRight.
func (a *StringArray) Append(value ...string) *StringArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Len returns the length of array.
func (a *StringArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *StringArray) Slice() []string {
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]string, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// Clone returns a new array, which is a copy of current array.
func (a *StringArray) Clone() (newArray *StringArray) {
a.mu.RLock()
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewStringArrayFrom(array, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *StringArray) Clear() *StringArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]string, 0)
}
a.mu.Unlock()
return a
}
// Contains checks whether a value exists in the array.
func (a *StringArray) Contains(value string) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *StringArray) Search(value string) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if strings.Compare(v, value) == 0 {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// Unique uniques the array, clear repeated items.
func (a *StringArray) Unique() *StringArray {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[:j], a.array[j+1:]...)
}
}
}
a.mu.Unlock()
return a
}
// LockFunc locks writing by callback function <f>.
func (a *StringArray) LockFunc(f func(array []string)) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *StringArray) Merge(array interface{}) *StringArray {
switch v := array.(type) {
case *Array:
a.Append(gconv.Strings(v.Slice())...)
case *IntArray:
a.Append(gconv.Strings(v.Slice())...)
case *StringArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedStringArray:
a.Append(gconv.Strings(v.Slice())...)
default:
a.Append(gconv.Strings(array)...)
}
return a
}
// Fill fills an array with num entries of the value <value>,
// keys starting at the <startIndex> parameter.
func (a *StringArray) Fill(startIndex int, num int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *StringArray) Chunk(size int) [][]string {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]string
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
}
// Pad pads array to the specified length with <value>.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// then no padding takes place.
func (a *StringArray) Pad(size int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]string, n)
for i := 0; i < n; i++ {
tmp[i] = value
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
return a
}
// Rand randomly returns one item from array(no deleting).
func (a *StringArray) Rand() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands randomly returns <size> items from array(no deleting).
func (a *StringArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
}
n := make([]string, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
}
return n
}
// Shuffle randomly shuffles the array.
func (a *StringArray) Shuffle() *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
return a
}
// Reverse makes array with elements in reverse order.
func (a *StringArray) Reverse() *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
return a
}
// Join joins array elements with a string <glue>.
func (a *StringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *StringArray) CountValues() map[string]int {
m := make(map[string]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *StringArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -1,216 +1,543 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/internal/rwmutex"
"bytes"
"fmt"
"math"
"sort"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
)
// 默认按照从低到高进行排序
// It's using increasing order in default.
type SortedIntArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
array []int // 底层数组
unique *gtype.Bool // 是否要求不能重复(默认false)
compareFunc func(v1, v2 int) int // 比较函数,返回值 -1: v1 < v20: v1 == v21: v1 > v2
mu *rwmutex.RWMutex
array []int
unique *gtype.Bool // Whether enable unique feature(false)
comparator func(v1, v2 int) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
}
// 创建一个排序的int数组
func NewSortedIntArray(cap int, unsafe...bool) *SortedIntArray {
return &SortedIntArray {
mu : rwmutex.New(unsafe...),
array : make([]int, 0, cap),
unique : gtype.NewBool(),
compareFunc : func(v1, v2 int) int {
if v1 < v2 {
return -1
}
if v1 > v2 {
return 1
}
return 0
},
}
// NewSortedIntArray creates and returns an empty sorted array.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedIntArray(unsafe ...bool) *SortedIntArray {
return NewSortedIntArraySize(0, unsafe...)
}
// 添加加数据项
func (a *SortedIntArray) Add(values...int) {
if len(values) == 0 {
return
}
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
continue
}
if index < 0 {
a.array = append(a.array, value)
continue
}
// 加到指定索引后面
if cmp > 0 {
index++
}
rear := append([]int{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
// NewSortedIntArraySize create and returns an sorted array with given size and cap.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedIntArraySize(cap int, unsafe ...bool) *SortedIntArray {
return &SortedIntArray{
mu: rwmutex.New(unsafe...),
array: make([]int, 0, cap),
unique: gtype.NewBool(),
comparator: func(v1, v2 int) int {
if v1 < v2 {
return -1
}
if v1 > v2 {
return 1
}
return 0
},
}
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// NewIntArrayFrom creates and returns an sorted array with given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedIntArrayFrom(array []int, unsafe ...bool) *SortedIntArray {
a := NewSortedIntArraySize(0, unsafe...)
a.array = array
sort.Ints(a.array)
return a
}
// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedIntArrayFromCopy(array []int, unsafe ...bool) *SortedIntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return NewSortedIntArrayFrom(newArray, unsafe...)
}
// SetArray sets the underlying slice array with the given <array>.
func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Ints(a.array)
return a
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// in increasing order(default) or decreasing order.
func (a *SortedIntArray) Sort() *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Ints(a.array)
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedIntArray) Add(values ...int) *SortedIntArray {
if len(values) == 0 {
return a
}
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
continue
}
if index < 0 {
a.array = append(a.array, value)
continue
}
if cmp > 0 {
index++
}
rear := append([]int{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
}
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 {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove removes an item by index.
func (a *SortedIntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// PopLeft pops and returns an item from the beginning of array.
func (a *SortedIntArray) PopLeft() int {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1:]
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// PopRight pops and returns an item from the end of array.
func (a *SortedIntArray) PopRight() int {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[:index]
return value
}
// 数组长度
// PopRand randomly pops and return an item out of array.
func (a *SortedIntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *SortedIntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
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:]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *SortedIntArray) PopLefts(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0:size]
a.array = a.array[size:]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *SortedIntArray) PopRights(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index:]
a.array = a.array[:index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedIntArray) Range(start int, end ...int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]int)(nil)
if a.mu.IsSafe() {
array = make([]int, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *SortedIntArray) SubSlice(offset int, length ...int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// Len returns the length of array.
func (a *SortedIntArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
// Sum returns the sum of values in an array.
func (a *SortedIntArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += v
}
return
}
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedIntArray) Slice() []int {
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]int, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]int, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedIntArray) Search(value int) (index int, result int) {
return a.binSearch(value, true)
// Contains checks whether a value exists in the array.
func (a *SortedIntArray) Contains(value int) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedIntArray) Search(value int) (index int) {
if i, r := a.binSearch(value, true); r == 0 {
return i
}
return -1
}
// Binary search.
// It returns the last compared index and the result.
// If <result> equals to 0, it means the value at <index> is equals to <value>.
// 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()
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 1 : min = mid + 1
case 0 :
return mid, cmp
}
}
return mid, cmp
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
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])
switch {
case cmp < 0:
max = mid - 1
case cmp > 0:
min = mid + 1
default:
return mid, cmp
}
}
return mid, cmp
}
// 设置是否允许数组唯一
func (a *SortedIntArray) SetUnique(unique bool) {
oldUnique := a.unique.Val()
a.unique.Set(unique)
if unique && oldUnique != unique {
a.doUnique()
}
// SetUnique sets unique mark to the array,
// 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)
if unique && oldUnique != unique {
a.Unique()
}
return a
}
// 清理数组中重复的元素项
func (a *SortedIntArray) doUnique() {
a.mu.Lock()
i := 0
for {
if i == len(a.array) - 1 {
break
}
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
} else {
i++
}
}
a.mu.Unlock()
// Unique uniques the array, clear repeated items.
func (a *SortedIntArray) Unique() *SortedIntArray {
a.mu.Lock()
i := 0
for {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
}
}
a.mu.Unlock()
return a
}
// 清空数据数组
func (a *SortedIntArray) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]int, 0, a.cap)
}
a.mu.Unlock()
// Clone returns a new array, which is a copy of current array.
func (a *SortedIntArray) Clone() (newArray *SortedIntArray) {
a.mu.RLock()
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedIntArrayFrom(array, !a.mu.IsSafe())
}
// 使用自定义方法执行加锁修改操作
func (a *SortedIntArray) LockFunc(f func(array []int)) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
// Clear deletes all items of current array.
func (a *SortedIntArray) Clear() *SortedIntArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]int, 0)
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁读取操作
func (a *SortedIntArray) RLockFunc(f func(array []int)) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}
// LockFunc locks writing by callback function <f>.
func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// 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 *StringArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedStringArray:
a.Add(gconv.Ints(v.Slice())...)
default:
a.Add(gconv.Ints(array)...)
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *SortedIntArray) Chunk(size int) [][]int {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]int
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedIntArray) Rand() int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// 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)
}
n := make([]int, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
}
return n
}
// Join joins array elements with a string <glue>.
func (a *SortedIntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *SortedIntArray) CountValues() map[int]int {
m := make(map[int]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *SortedIntArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -1,209 +1,544 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/internal/rwmutex"
"bytes"
"fmt"
"math"
"sort"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
)
// 默认按照从低到高进行排序
// It's using increasing order in default.
type SortedArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
array []interface{} // 底层数组
unique *gtype.Bool // 是否要求不能重复
compareFunc func(v1, v2 interface{}) int // 比较函数,返回值 -1: v1 < v20: v1 == v21: v1 > v2
mu *rwmutex.RWMutex
array []interface{}
unique *gtype.Bool // Whether enable unique feature(false)
comparator func(v1, v2 interface{}) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
}
func NewSortedArray(cap int, compareFunc func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
return &SortedArray{
mu : rwmutex.New(unsafe...),
unique : gtype.NewBool(),
array : make([]interface{}, 0, cap),
compareFunc : compareFunc,
}
// NewSortedArray creates and returns an empty sorted array.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety, which is false in default.
// The parameter <comparator> used to compare values to sort in array,
// if it returns value < 0, means v1 < v2;
// if it returns value = 0, means v1 = v2;
// if it returns value > 0, means v1 > v2;
func NewSortedArray(comparator func(v1, v2 interface{}) int, unsafe ...bool) *SortedArray {
return NewSortedArraySize(0, comparator, unsafe...)
}
// 添加加数据项
func (a *SortedArray) Add(values...interface{}) {
if len(values) == 0 {
return
}
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
continue
}
if index < 0 {
a.array = append(a.array, value)
continue
}
// 加到指定索引后面
if cmp > 0 {
index++
}
rear := append([]interface{}{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
// NewSortedArraySize create and returns an sorted array with given size and cap.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedArraySize(cap int, comparator func(v1, v2 interface{}) int, unsafe ...bool) *SortedArray {
return &SortedArray{
mu: rwmutex.New(unsafe...),
unique: gtype.NewBool(),
array: make([]interface{}, 0, cap),
comparator: comparator,
}
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// NewSortedArrayFrom creates and returns an sorted array with given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedArrayFrom(array []interface{}, comparator func(v1, v2 interface{}) int, unsafe ...bool) *SortedArray {
a := NewSortedArraySize(0, comparator, unsafe...)
a.array = array
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
})
return a
}
// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedArrayFromCopy(array []interface{}, comparator func(v1, v2 interface{}) int, unsafe ...bool) *SortedArray {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return NewSortedArrayFrom(newArray, comparator, unsafe...)
}
// SetArray sets the underlying slice array with the given <array>.
func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
a.mu.Lock()
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
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// in increasing order(default) or decreasing order
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
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedArray) Add(values ...interface{}) *SortedArray {
if len(values) == 0 {
return a
}
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
continue
}
if index < 0 {
a.array = append(a.array, value)
continue
}
if cmp > 0 {
index++
}
rear := append([]interface{}{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
}
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{} {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove removes an item by index.
func (a *SortedArray) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// PopLeft pops and returns an item from the beginning of array.
func (a *SortedArray) PopLeft() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1:]
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// PopRight pops and returns an item from the end of array.
func (a *SortedArray) PopRight() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[:index]
return value
}
// 数组长度
// PopRand randomly pops and return an item out of array.
func (a *SortedArray) PopRand() interface{} {
return a.Remove(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)
}
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:]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *SortedArray) PopLefts(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0:size]
a.array = a.array[size:]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *SortedArray) PopRights(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index:]
a.array = a.array[:index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedArray) Range(start int, end ...int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]interface{})(nil)
if a.mu.IsSafe() {
array = make([]interface{}, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *SortedArray) SubSlice(offset int, length ...int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]interface{}, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// Sum returns the sum of values in an array.
func (a *SortedArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Len returns the length of array.
func (a *SortedArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedArray) Slice() []interface{} {
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]interface{}, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]interface{}, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedArray) Search(value interface{}) (index int, result int) {
return a.binSearch(value, true)
// Contains checks whether a value exists in the array.
func (a *SortedArray) Contains(value interface{}) bool {
return a.Search(value) != -1
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
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()
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 1 : min = mid + 1
case 0 :
return mid, cmp
}
}
return mid, cmp
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedArray) Search(value interface{}) (index int) {
if i, r := a.binSearch(value, true); r == 0 {
return i
}
return -1
}
// 设置是否允许数组唯一
func (a *SortedArray) SetUnique(unique bool) {
oldUnique := a.unique.Val()
a.unique.Set(unique)
if unique && oldUnique != unique {
a.doUnique()
}
// Binary search.
// It returns the last compared index and the result.
// If <result> equals to 0, it means the value at <index> is equals to <value>.
// 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()
}
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])
switch {
case cmp < 0:
max = mid - 1
case cmp > 0:
min = mid + 1
default:
return mid, cmp
}
}
return mid, cmp
}
// 清理数组中重复的元素项
func (a *SortedArray) doUnique() {
a.mu.Lock()
defer a.mu.Unlock()
i := 0
for {
if i == len(a.array) - 1 {
break
}
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
} else {
i++
}
}
// SetUnique sets unique mark to the array,
// 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)
if unique && oldUnique != unique {
a.Unique()
}
return a
}
// 清空数据数组
func (a *SortedArray) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]interface{}, 0, a.cap)
}
a.mu.Unlock()
// Unique uniques the array, clear repeated items.
func (a *SortedArray) Unique() *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
i := 0
for {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
}
}
return a
}
// 使用自定义方法执行加锁修改操作
func (a *SortedArray) LockFunc(f func(array []interface{})) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
// Clone returns a new array, which is a copy of current array.
func (a *SortedArray) Clone() (newArray *SortedArray) {
a.mu.RLock()
array := make([]interface{}, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedArrayFrom(array, a.comparator, !a.mu.IsSafe())
}
// 使用自定义方法执行加锁读取操作
func (a *SortedArray) RLockFunc(f func(array []interface{})) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}
// Clear deletes all items of current array.
func (a *SortedArray) Clear() *SortedArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]interface{}, 0)
}
a.mu.Unlock()
return a
}
// LockFunc locks writing by callback function <f>.
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// 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 *StringArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedStringArray:
a.Add(gconv.Interfaces(v.Slice())...)
default:
a.Add(gconv.Interfaces(array)...)
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *SortedArray) Chunk(size int) [][]interface{} {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]interface{}
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedArray) Rand() interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// 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)
}
n := make([]interface{}, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
}
return n
}
// Join joins array elements with a string <glue>.
func (a *SortedArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *SortedArray) CountValues() map[interface{}]int {
m := make(map[interface{}]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *SortedArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -1,210 +1,538 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"gitee.com/johng/gf/g/container/gtype"
"strings"
"gitee.com/johng/gf/g/internal/rwmutex"
"bytes"
"fmt"
"math"
"sort"
"strings"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
)
// 默认按照从低到高进行排序
// It's using increasing order in default.
type SortedStringArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
array []string // 底层数组
unique *gtype.Bool // 是否要求不能重复
compareFunc func(v1, v2 string) int // 比较函数,返回值 -1: v1 < v20: v1 == v21: v1 > v2
mu *rwmutex.RWMutex
array []string
unique *gtype.Bool // Whether enable unique feature(false)
comparator func(v1, v2 string) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
}
func NewSortedStringArray(cap int, unsafe...bool) *SortedStringArray {
return &SortedStringArray {
mu : rwmutex.New(unsafe...),
array : make([]string, 0, cap),
unique : gtype.NewBool(),
compareFunc : func(v1, v2 string) int {
return strings.Compare(v1, v2)
},
}
// NewSortedStringArray creates and returns an empty sorted array.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedStringArray(unsafe ...bool) *SortedStringArray {
return NewSortedStringArraySize(0, unsafe...)
}
// 添加加数据项
func (a *SortedStringArray) Add(values...string) {
if len(values) == 0 {
return
}
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
continue
}
if index < 0 {
a.array = append(a.array, value)
continue
}
// 加到指定索引后面
if cmp > 0 {
index++
}
rear := append([]string{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
// NewSortedStringArraySize create and returns an sorted array with given size and cap.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedStringArraySize(cap int, unsafe ...bool) *SortedStringArray {
return &SortedStringArray{
mu: rwmutex.New(unsafe...),
array: make([]string, 0, cap),
unique: gtype.NewBool(),
comparator: func(v1, v2 string) int {
return strings.Compare(v1, v2)
},
}
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// NewSortedStringArrayFrom creates and returns an sorted array with given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedStringArrayFrom(array []string, unsafe ...bool) *SortedStringArray {
a := NewSortedStringArraySize(0, unsafe...)
a.array = array
sort.Strings(a.array)
return a
}
// NewSortedStringArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The parameter <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedStringArrayFromCopy(array []string, unsafe ...bool) *SortedStringArray {
newArray := make([]string, len(array))
copy(newArray, array)
return NewSortedStringArrayFrom(newArray, unsafe...)
}
// SetArray sets the underlying slice array with the given <array>.
func (a *SortedStringArray) SetArray(array []string) *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Strings(a.array)
return a
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// in increasing order(default) or decreasing order.
func (a *SortedStringArray) Sort() *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Strings(a.array)
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedStringArray) Add(values ...string) *SortedStringArray {
if len(values) == 0 {
return a
}
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
continue
}
if index < 0 {
a.array = append(a.array, value)
continue
}
if cmp > 0 {
index++
}
rear := append([]string{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
}
return a
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *SortedStringArray) Get(index int) string {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove removes an item by index.
func (a *SortedStringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// PopLeft pops and returns an item from the beginning of array.
func (a *SortedStringArray) PopLeft() string {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1:]
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// PopRight pops and returns an item from the end of array.
func (a *SortedStringArray) PopRight() string {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[:index]
return value
}
// 数组长度
// PopRand randomly pops and return an item out of array.
func (a *SortedStringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *SortedStringArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
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:]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *SortedStringArray) PopLefts(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0:size]
a.array = a.array[size:]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *SortedStringArray) PopRights(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index:]
a.array = a.array[:index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedStringArray) Range(start int, end ...int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]string)(nil)
if a.mu.IsSafe() {
array = make([]string, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *SortedStringArray) SubSlice(offset int, length ...int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// Sum returns the sum of values in an array.
func (a *SortedStringArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Len returns the length of array.
func (a *SortedStringArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedStringArray) Slice() []string {
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]string, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]string, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedStringArray) Search(value string) (index int, result int) {
return a.binSearch(value, true)
// Contains checks whether a value exists in the array.
func (a *SortedStringArray) Contains(value string) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedStringArray) Search(value string) (index int) {
if i, r := a.binSearch(value, true); r == 0 {
return i
}
return -1
}
// Binary search.
// It returns the last compared index and the result.
// If <result> equals to 0, it means the value at <index> is equals to <value>.
// 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 *SortedStringArray) 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()
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 1 : min = mid + 1
case 0 :
return mid, cmp
}
}
return mid, cmp
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
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])
switch {
case cmp < 0:
max = mid - 1
case cmp > 0:
min = mid + 1
default:
return mid, cmp
}
}
return mid, cmp
}
// 设置是否允许数组唯一
func (a *SortedStringArray) SetUnique(unique bool) {
oldUnique := a.unique.Val()
a.unique.Set(unique)
if unique && oldUnique != unique {
a.doUnique()
}
// SetUnique sets unique mark to the array,
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedStringArray) SetUnique(unique bool) *SortedStringArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
if unique && oldUnique != unique {
a.Unique()
}
return a
}
// 清理数组中重复的元素项
func (a *SortedStringArray) doUnique() {
a.mu.Lock()
i := 0
for {
if i == len(a.array) - 1 {
break
}
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
} else {
i++
}
}
a.mu.Unlock()
// Unique uniques the array, clear repeated items.
func (a *SortedStringArray) Unique() *SortedStringArray {
a.mu.Lock()
i := 0
for {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
}
}
a.mu.Unlock()
return a
}
// 清空数据数组
func (a *SortedStringArray) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]string, 0, a.cap)
}
a.mu.Unlock()
// Clone returns a new array, which is a copy of current array.
func (a *SortedStringArray) Clone() (newArray *SortedStringArray) {
a.mu.RLock()
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedStringArrayFrom(array, !a.mu.IsSafe())
}
// 使用自定义方法执行加锁修改操作
func (a *SortedStringArray) LockFunc(f func(array []string)) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
// Clear deletes all items of current array.
func (a *SortedStringArray) Clear() *SortedStringArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]string, 0)
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁读取操作
func (a *SortedStringArray) RLockFunc(f func(array []string)) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}
// LockFunc locks writing by callback function <f>.
func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedStringArray) Merge(array interface{}) *SortedStringArray {
switch v := array.(type) {
case *Array:
a.Add(gconv.Strings(v.Slice())...)
case *IntArray:
a.Add(gconv.Strings(v.Slice())...)
case *StringArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedStringArray:
a.Add(gconv.Strings(v.Slice())...)
default:
a.Add(gconv.Strings(array)...)
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *SortedStringArray) Chunk(size int) [][]string {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]string
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedStringArray) Rand() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands randomly returns <size> items from array(no deleting).
func (a *SortedStringArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
}
n := make([]string, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
}
return n
}
// Join joins array elements with a string <glue>.
func (a *SortedStringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *SortedStringArray) CountValues() map[string]int {
m := make(map[string]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *SortedStringArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -1,175 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package garray
import (
"strings"
"gitee.com/johng/gf/g/internal/rwmutex"
)
type StringArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
size int // 初始化设置的数组大小
array []string // 底层数组
}
func NewStringArray(size int, cap int, unsafe...bool) *StringArray {
a := &StringArray{
mu : rwmutex.New(unsafe...),
}
a.size = size
if cap > 0 {
a.cap = cap
a.array = make([]string, size, cap)
} else {
a.array = make([]string, size)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
func (a *StringArray) Get(index int) string {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 设置指定索引的数据项, 调用方注意判断数组边界
func (a *StringArray) Set(index int, value string) {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *StringArray) InsertBefore(index int, value string) {
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...)
}
// 在当前索引位置后插入一个数据项, 调用方注意判断数组边界
func (a *StringArray) InsertAfter(index int, value string) {
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...)
}
// 删除指定索引的数据项, 调用方注意判断数组边界
func (a *StringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 追加数据项
func (a *StringArray) Append(value...string) {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
}
// 数组长度
func (a *StringArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
func (a *StringArray) Slice() []string {
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]string, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
}
// 清空数据数组
func (a *StringArray) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]string, a.size, a.cap)
} else {
a.array = make([]string, a.size)
}
}
a.mu.Unlock()
}
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
func (a *StringArray) Search(value string) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if strings.Compare(v, value) == 0 {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// 清理数组中重复的元素项
func (a *StringArray) Unique() *StringArray {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *StringArray) LockFunc(f func(array []string)) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
}
// 使用自定义方法执行加锁读取操作
func (a *StringArray) RLockFunc(f func(array []string)) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}

View File

@ -1,43 +1,43 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package garray_test
import (
"gitee.com/johng/gf/g/container/garray"
"testing"
"github.com/gogf/gf/g/container/garray"
"testing"
)
var (
sortedIntArray = garray.NewSortedIntArray(0)
sortedIntArray = garray.NewSortedIntArray()
)
func BenchmarkSortedIntArray_Add(b *testing.B) {
b.N = 1000
for i := 0; i < b.N; i++ {
sortedIntArray.Add(i)
}
b.N = 1000
for i := 0; i < b.N; i++ {
sortedIntArray.Add(i)
}
}
func BenchmarkSortedIntArray_Search(b *testing.B) {
for i := 0; i < b.N; i++ {
sortedIntArray.Search(i)
}
for i := 0; i < b.N; i++ {
sortedIntArray.Search(i)
}
}
func BenchmarkSortedIntArray_PopLeft(b *testing.B) {
for i := 0; i < b.N; i++ {
sortedIntArray.PopLeft()
}
for i := 0; i < b.N; i++ {
sortedIntArray.PopLeft()
}
}
func BenchmarkSortedIntArray_PopRight(b *testing.B) {
for i := 0; i < b.N; i++ {
sortedIntArray.PopLeft()
}
for i := 0; i < b.N; i++ {
sortedIntArray.PopLeft()
}
}

View File

@ -0,0 +1,111 @@
// 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 garray_test
import (
"fmt"
"github.com/gogf/gf/g/container/garray"
)
func Example_basic() {
// 创建普通的数组,默认并发安全(带锁)
a := garray.New()
// 添加数据项
for i := 0; i < 10; i++ {
a.Append(i)
}
// 获取当前数组长度
fmt.Println(a.Len())
// 获取当前数据项列表
fmt.Println(a.Slice())
// 获取指定索引项
fmt.Println(a.Get(6))
// 查找指定数据项是否存在
fmt.Println(a.Contains(6))
fmt.Println(a.Contains(100))
// 在指定索引前插入数据项
a.InsertAfter(9, 11)
// 在指定索引后插入数据项
a.InsertBefore(10, 10)
fmt.Println(a.Slice())
// 修改指定索引的数据项
a.Set(0, 100)
fmt.Println(a.Slice())
// 搜索数据项,返回搜索到的索引位置
fmt.Println(a.Search(5))
// 删除指定索引的数据项
a.Remove(0)
fmt.Println(a.Slice())
// 清空数组
fmt.Println(a.Slice())
a.Clear()
fmt.Println(a.Slice())
// Output:
// 10
// [0 1 2 3 4 5 6 7 8 9]
// 6
// true
// false
// [0 1 2 3 4 5 6 7 8 9 10 11]
// [100 1 2 3 4 5 6 7 8 9 10 11]
// 5
// [1 2 3 4 5 6 7 8 9 10 11]
// [1 2 3 4 5 6 7 8 9 10 11]
// []
}
func Example_rand() {
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
// 随机返回两个数据项(不删除)
fmt.Println(array.Rands(2))
fmt.Println(array.PopRand())
}
func Example_pop() {
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
fmt.Println(array.PopLeft())
fmt.Println(array.PopLefts(2))
fmt.Println(array.PopRight())
fmt.Println(array.PopRights(2))
// Output:
// 1
// [2 3]
// 9
// [7 8]
}
func Example_merge() {
array1 := garray.NewFrom([]interface{}{1, 2})
array2 := garray.NewFrom([]interface{}{3, 4})
slice1 := []interface{}{5, 6}
slice2 := []int{7, 8}
slice3 := []string{"9", "0"}
fmt.Println(array1.Slice())
array1.Merge(array1)
array1.Merge(array2)
array1.Merge(slice1)
array1.Merge(slice2)
array1.Merge(slice3)
fmt.Println(array1.Slice())
// Output:
// [1 2]
// [1 2 1 2 3 4 5 6 7 8 9 0]
}

View File

@ -0,0 +1,94 @@
// 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.
// go test *.go
package garray_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"strings"
"testing"
)
func Test_IntArray_Unique(t *testing.T) {
expect := []int{1, 2, 3, 4, 5, 6}
array := garray.NewIntArray()
array.Append(1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6)
array.Unique()
gtest.Assert(array.Slice(), expect)
}
func Test_SortedIntArray1(t *testing.T) {
expect := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
array := garray.NewSortedIntArray()
for i := 10; i > -1; i-- {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedIntArray2(t *testing.T) {
expect := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
array := garray.NewSortedIntArray()
for i := 0; i <= 10; i++ {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedStringArray1(t *testing.T) {
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
array := garray.NewSortedStringArray()
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedStringArray2(t *testing.T) {
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
array := garray.NewSortedStringArray()
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray1(t *testing.T) {
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
array := garray.NewSortedArray(func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray2(t *testing.T) {
expect := []string{"0", "1", "10", "2", "3", "4", "5", "6", "7", "8", "9"}
array := garray.NewSortedArray(func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func TestNewFromCopy(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"100", "200", "300", "400", "500", "600"}
array1 := garray.NewFromCopy(a1)
gtest.AssertIN(array1.PopRands(2), a1)
gtest.Assert(len(array1.PopRands(1)), 1)
gtest.Assert(len(array1.PopRands(9)), 3)
})
}

View File

@ -0,0 +1,623 @@
// 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.
// go test *.go
package garray_test
import (
"testing"
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
)
func Test_IntArray_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []int{0, 1, 2, 3}
array := garray.NewIntArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
array.Set(0, 100)
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
gtest.Assert(array.Search(100), 0)
gtest.Assert(array.Contains(100), true)
gtest.Assert(array.Remove(0), 100)
gtest.Assert(array.Contains(100), false)
array.Append(4)
gtest.Assert(array.Len(), 4)
array.InsertBefore(0, 100)
array.InsertAfter(0, 200)
gtest.Assert(array.Slice(), []int{100, 200, 1, 2, 3, 4})
array.InsertBefore(5, 300)
array.InsertAfter(6, 400)
gtest.Assert(array.Slice(), []int{100, 200, 1, 2, 3, 300, 4, 400})
gtest.Assert(array.Clear().Len(), 0)
})
}
func TestIntArray_Sort(t *testing.T) {
gtest.Case(t, func() {
expect1 := []int{0, 1, 2, 3}
expect2 := []int{3, 2, 1, 0}
array := garray.NewIntArray()
for i := 3; i >= 0; i-- {
array.Append(i)
}
array.Sort()
gtest.Assert(array.Slice(), expect1)
array.Sort(true)
gtest.Assert(array.Slice(), expect2)
})
}
func TestIntArray_Unique(t *testing.T) {
gtest.Case(t, func() {
expect := []int{1, 1, 2, 3}
array := garray.NewIntArrayFrom(expect)
gtest.Assert(array.Unique().Slice(), []int{1, 2, 3})
})
}
func TestIntArray_PushAndPop(t *testing.T) {
gtest.Case(t, func() {
expect := []int{0, 1, 2, 3}
array := garray.NewIntArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.PopLeft(), 0)
gtest.Assert(array.PopRight(), 3)
gtest.AssertIN(array.PopRand(), []int{1, 2})
gtest.AssertIN(array.PopRand(), []int{1, 2})
gtest.Assert(array.Len(), 0)
array.PushLeft(1).PushRight(2)
gtest.Assert(array.Slice(), []int{1, 2})
})
}
func TestIntArray_PopLeftsAndPopRights(t *testing.T) {
gtest.Case(t, func() {
value1 := []int{0, 1, 2, 3, 4, 5, 6}
value2 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(value1)
array2 := garray.NewIntArrayFrom(value2)
gtest.Assert(array1.PopLefts(2), []int{0, 1})
gtest.Assert(array1.Slice(), []int{2, 3, 4, 5, 6})
gtest.Assert(array1.PopRights(2), []int{5, 6})
gtest.Assert(array1.Slice(), []int{2, 3, 4})
gtest.Assert(array1.PopRights(20), []int{2, 3, 4})
gtest.Assert(array1.Slice(), []int{})
gtest.Assert(array2.PopLefts(20), []int{0, 1, 2, 3, 4, 5, 6})
gtest.Assert(array2.Slice(), []int{})
})
}
func TestIntArray_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(value1)
gtest.Assert(array1.Range(0, 1), []int{0})
gtest.Assert(array1.Range(1, 2), []int{1})
gtest.Assert(array1.Range(0, 2), []int{0, 1})
gtest.Assert(array1.Range(10, 2), nil)
gtest.Assert(array1.Range(-1, 10), value1)
})
}
func TestIntArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3}
a2 := []int{4, 5, 6, 7}
array1 := garray.NewIntArrayFrom(a1)
array2 := garray.NewIntArrayFrom(a2)
gtest.Assert(array1.Merge(array2).Slice(), []int{0, 1, 2, 3, 4, 5, 6, 7})
})
}
func TestIntArray_Fill(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0}
a2 := []int{0}
array1 := garray.NewIntArrayFrom(a1)
array2 := garray.NewIntArrayFrom(a2)
gtest.Assert(array1.Fill(1, 2, 100).Slice(), []int{0, 100, 100})
gtest.Assert(array2.Fill(0, 2, 100).Slice(), []int{100, 100})
})
}
func TestIntArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 4, 5}
array1 := garray.NewIntArrayFrom(a1)
chunks := array1.Chunk(2)
gtest.Assert(len(chunks), 3)
gtest.Assert(chunks[0], []int{1, 2})
gtest.Assert(chunks[1], []int{3, 4})
gtest.Assert(chunks[2], []int{5})
})
}
func TestIntArray_Pad(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Pad(3, 1).Slice(), []int{0, 1, 1})
gtest.Assert(array1.Pad(-4, 1).Slice(), []int{1, 0, 1, 1})
gtest.Assert(array1.Pad(3, 1).Slice(), []int{1, 0, 1, 1})
})
}
func TestIntArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.SubSlice(6), []int{6})
gtest.Assert(array1.SubSlice(5), []int{5, 6})
gtest.Assert(array1.SubSlice(8), nil)
gtest.Assert(array1.SubSlice(0, 2), []int{0, 1})
gtest.Assert(array1.SubSlice(2, 2), []int{2, 3})
gtest.Assert(array1.SubSlice(5, 8), []int{5, 6})
gtest.Assert(array1.SubSlice(-1, 1), []int{6})
gtest.Assert(array1.SubSlice(-1, 9), []int{6})
gtest.Assert(array1.SubSlice(-2, 3), []int{5, 6})
gtest.Assert(array1.SubSlice(-7, 3), []int{0, 1, 2})
gtest.Assert(array1.SubSlice(-8, 3), nil)
gtest.Assert(array1.SubSlice(-1, -3), []int{3, 4, 5})
gtest.Assert(array1.SubSlice(-9, 3), nil)
gtest.Assert(array1.SubSlice(1, -1), []int{0})
gtest.Assert(array1.SubSlice(1, -3), nil)
})
}
func TestIntArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(len(array1.Rands(2)), 2)
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
gtest.AssertIN(array1.Rand(), a1)
})
}
func TestIntArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{100, 200, 300, 400, 500, 600}
array := garray.NewIntArrayFrom(a1)
ns1 := array.PopRands(2)
gtest.AssertIN(ns1, []int{100, 200, 300, 400, 500, 600})
gtest.AssertIN(len(ns1), 2)
ns2 := array.PopRands(7)
gtest.AssertIN(len(ns2), 6)
gtest.AssertIN(ns2, []int{100, 200, 300, 400, 500, 600})
})
}
func TestIntArray_Shuffle(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Shuffle().Len(), 7)
})
}
func TestIntArray_Reverse(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Reverse().Slice(), []int{6, 5, 4, 3, 2, 1, 0})
})
}
func TestIntArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
})
}
func TestNewSortedIntArrayFrom(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 3, 2, 1, 4, 5, 6}
array1 := garray.NewSortedIntArrayFrom(a1, true)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
})
}
func TestNewSortedIntArrayFromCopy(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 5, 2, 1, 4, 3, 6}
array1 := garray.NewSortedIntArrayFromCopy(a1, false)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
})
}
func TestSortedIntArray_SetArray(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3}
a2 := []int{4, 5, 6}
array1 := garray.NewSortedIntArrayFrom(a1)
array2 := array1.SetArray(a2)
gtest.Assert(array2.Len(), 3)
gtest.Assert(array2.Search(3), -1)
gtest.Assert(array2.Search(5), 1)
gtest.Assert(array2.Search(6), 2)
})
}
func TestSortedIntArray_Sort(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 3, 2, 1}
array1 := garray.NewSortedIntArrayFrom(a1)
array2 := array1.Sort()
gtest.Assert(array2.Len(), 4)
gtest.Assert(array2, []int{0, 1, 2, 3})
})
}
func TestSortedIntArray_Get(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 0}
array1 := garray.NewSortedIntArrayFrom(a1)
gtest.Assert(array1.Get(0), 0)
gtest.Assert(array1.Get(1), 1)
gtest.Assert(array1.Get(3), 5)
})
}
func TestSortedIntArray_Remove(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 0}
array1 := garray.NewSortedIntArrayFrom(a1)
i1 := array1.Remove(2)
gtest.Assert(i1, 3)
gtest.Assert(array1.Search(5), 2)
// 再次删除剩下的数组中的第一个
i2 := array1.Remove(0)
gtest.Assert(i2, 0)
gtest.Assert(array1.Search(5), 1)
a2 := []int{1, 3, 4}
array2 := garray.NewSortedIntArrayFrom(a2)
i3 := array2.Remove(1)
gtest.Assert(array2.Search(1), 0)
gtest.Assert(i3, 3)
i3 = array2.Remove(1)
gtest.Assert(array2.Search(4), -1)
gtest.Assert(i3, 4)
})
}
func TestSortedIntArray_PopLeft(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
i1 := array1.PopLeft()
gtest.Assert(i1, 1)
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1.Search(1), -1)
})
}
func TestSortedIntArray_PopRight(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
i1 := array1.PopRight()
gtest.Assert(i1, 5)
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1.Search(5), -1)
})
}
func TestSortedIntArray_PopRand(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
i1 := array1.PopRand()
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1.Search(i1), -1)
gtest.AssertIN(i1, []int{1, 3, 5, 2})
})
}
func TestSortedIntArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.PopRands(2)
gtest.Assert(array1.Len(), 2)
gtest.AssertIN(ns1, []int{1, 3, 5, 2})
a2 := []int{1, 3, 5, 2}
array2 := garray.NewSortedIntArrayFrom(a2)
ns2 := array2.PopRands(5)
gtest.Assert(array2.Len(), 0)
gtest.Assert(len(ns2), 4)
gtest.AssertIN(ns2, []int{1, 3, 5, 2})
})
}
func TestSortedIntArray_PopLefts(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.PopLefts(2)
gtest.Assert(array1.Len(), 2)
gtest.Assert(ns1, []int{1, 2})
a2 := []int{1, 3, 5, 2}
array2 := garray.NewSortedIntArrayFrom(a2)
ns2 := array2.PopLefts(5)
gtest.Assert(array2.Len(), 0)
gtest.AssertIN(ns2, []int{1, 3, 5, 2})
})
}
func TestSortedIntArray_PopRights(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.PopRights(2)
gtest.Assert(array1.Len(), 2)
gtest.Assert(ns1, []int{3, 5})
a2 := []int{1, 3, 5, 2}
array2 := garray.NewSortedIntArrayFrom(a2)
ns2 := array2.PopRights(5)
gtest.Assert(array2.Len(), 0)
gtest.AssertIN(ns2, []int{1, 3, 5, 2})
})
}
func TestSortedIntArray_Range(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2, 6, 7}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.Range(1, 4)
gtest.Assert(len(ns1), 3)
gtest.Assert(ns1, []int{2, 3, 5})
ns2 := array1.Range(5, 4)
gtest.Assert(len(ns2), 0)
ns3 := array1.Range(-1, 4)
gtest.Assert(len(ns3), 4)
nsl := array1.Range(5, 8)
gtest.Assert(len(nsl), 1)
})
}
func TestSortedIntArray_Sum(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
n1 := array1.Sum()
gtest.Assert(n1, 9)
})
}
func TestSortedIntArray_Contains(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
//gtest.Assert(array1.Contains(3),true) //todo 这一行应该返回true
gtest.Assert(array1.Contains(4), false)
})
}
func TestSortedIntArray_Clone(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
array2 := array1.Clone()
gtest.Assert(array2.Len(), 3)
gtest.Assert(array2, array1)
})
}
func TestSortedIntArray_Clear(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 3, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
array1.Clear()
gtest.Assert(array1.Len(), 0)
})
}
func TestSortedIntArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 4, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.Chunk(2) //按每几个元素切成一个数组
ns2 := array1.Chunk(-1)
gtest.Assert(len(ns1), 3)
gtest.Assert(ns1[0], []int{1, 2})
gtest.Assert(ns1[2], []int{5})
gtest.Assert(len(ns2), 0)
})
}
func TestSortedIntArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 4, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.SubSlice(1, 2)
gtest.Assert(len(ns1), 2)
gtest.Assert(ns1, []int{2, 3})
ns2 := array1.SubSlice(7, 2)
gtest.Assert(len(ns2), 0)
ns3 := array1.SubSlice(3, 5)
gtest.Assert(len(ns3), 2)
gtest.Assert(ns3, []int{4, 5})
ns4 := array1.SubSlice(3, 1)
gtest.Assert(len(ns4), 1)
gtest.Assert(ns4, []int{4})
})
}
func TestSortedIntArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 4, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.Rand() //按每几个元素切成一个数组
gtest.AssertIN(ns1, a1)
})
}
func TestSortedIntArray_Rands(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 4, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.Rands(2) //按每几个元素切成一个数组
gtest.AssertIN(ns1, a1)
gtest.Assert(len(ns1), 2)
ns2 := array1.Rands(6) //按每几个元素切成一个数组
gtest.AssertIN(ns2, a1)
gtest.Assert(len(ns2), 5)
})
}
func TestSortedIntArray_CountValues(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 4, 5, 3}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.CountValues() //按每几个元素切成一个数组
gtest.Assert(len(ns1), 5)
gtest.Assert(ns1[2], 1)
gtest.Assert(ns1[3], 2)
})
}
func TestSortedIntArray_SetUnique(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 4, 5, 3}
array1 := garray.NewSortedIntArrayFrom(a1)
array1.SetUnique(true)
gtest.Assert(array1.Len(), 5)
gtest.Assert(array1, []int{1, 2, 3, 4, 5})
})
}
func TestIntArray_SetArray(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5}
a2 := []int{6, 7}
array1 := garray.NewIntArrayFrom(a1)
array1.SetArray(a2)
gtest.Assert(array1.Len(), 2)
gtest.Assert(array1, []int{6, 7})
})
}
func TestIntArray_Replace(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5}
a2 := []int{6, 7}
a3 := []int{9, 10, 11, 12, 13}
array1 := garray.NewIntArrayFrom(a1)
array1.Replace(a2)
gtest.Assert(array1, []int{6, 7, 3, 5})
array1.Replace(a3)
gtest.Assert(array1, []int{9, 10, 11, 12})
})
}
func TestIntArray_Clear(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5}
array1 := garray.NewIntArrayFrom(a1)
array1.Clear()
gtest.Assert(array1.Len(), 0)
})
}
func TestIntArray_Clone(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5}
array1 := garray.NewIntArrayFrom(a1)
array2 := array1.Clone()
gtest.Assert(array1, array2)
})
}
func TestArray_Get(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Get(2), 3)
gtest.Assert(array1.Len(), 4)
})
}
func TestIntArray_Sum(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Sum(), 11)
})
}
func TestIntArray_CountValues(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5, 3}
array1 := garray.NewIntArrayFrom(a1)
m1 := array1.CountValues()
gtest.Assert(len(m1), 4)
gtest.Assert(m1[1], 1)
gtest.Assert(m1[3], 2)
})
}
func TestNewIntArrayFromCopy(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5, 3}
array1 := garray.NewIntArrayFromCopy(a1)
gtest.Assert(array1.Len(), 5)
gtest.Assert(array1, a1)
})
}
func TestIntArray_Remove(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1, 2, 3, 5, 4}
array1 := garray.NewIntArrayFrom(a1)
n1 := array1.Remove(1)
gtest.Assert(n1, 2)
gtest.Assert(array1.Len(), 4)
n1 = array1.Remove(0)
gtest.Assert(n1, 1)
gtest.Assert(array1.Len(), 3)
n1 = array1.Remove(2)
gtest.Assert(n1, 4)
gtest.Assert(array1.Len(), 2)
})
}

View File

@ -0,0 +1,678 @@
// 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.
// go test *.go
package garray_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"strings"
"testing"
)
func Test_Array_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []interface{}{0, 1, 2, 3}
array := garray.NewArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
array.Set(0, 100)
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
gtest.Assert(array.Search(100), 0)
gtest.Assert(array.Contains(100), true)
gtest.Assert(array.Remove(0), 100)
gtest.Assert(array.Contains(100), false)
array.Append(4)
gtest.Assert(array.Len(), 4)
array.InsertBefore(0, 100)
array.InsertAfter(0, 200)
gtest.Assert(array.Slice(), []interface{}{100, 200, 1, 2, 3, 4})
array.InsertBefore(5, 300)
array.InsertAfter(6, 400)
gtest.Assert(array.Slice(), []interface{}{100, 200, 1, 2, 3, 300, 4, 400})
gtest.Assert(array.Clear().Len(), 0)
})
}
func TestArray_Sort(t *testing.T) {
gtest.Case(t, func() {
expect1 := []interface{}{0, 1, 2, 3}
expect2 := []interface{}{3, 2, 1, 0}
array := garray.NewArray()
for i := 3; i >= 0; i-- {
array.Append(i)
}
array.SortFunc(func(v1, v2 interface{}) bool {
return v1.(int) < v2.(int)
})
gtest.Assert(array.Slice(), expect1)
array.SortFunc(func(v1, v2 interface{}) bool {
return v1.(int) > v2.(int)
})
gtest.Assert(array.Slice(), expect2)
})
}
func TestArray_Unique(t *testing.T) {
gtest.Case(t, func() {
expect := []interface{}{1, 1, 2, 3}
array := garray.NewArrayFrom(expect)
gtest.Assert(array.Unique().Slice(), []interface{}{1, 2, 3})
})
}
func TestArray_PushAndPop(t *testing.T) {
gtest.Case(t, func() {
expect := []interface{}{0, 1, 2, 3}
array := garray.NewArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.PopLeft(), 0)
gtest.Assert(array.PopRight(), 3)
gtest.AssertIN(array.PopRand(), []interface{}{1, 2})
gtest.AssertIN(array.PopRand(), []interface{}{1, 2})
gtest.Assert(array.Len(), 0)
array.PushLeft(1).PushRight(2)
gtest.Assert(array.Slice(), []interface{}{1, 2})
})
}
func TestArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{100, 200, 300, 400, 500, 600}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}
func TestArray_PopLeftsAndPopRights(t *testing.T) {
gtest.Case(t, func() {
value1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
value2 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(value1)
array2 := garray.NewArrayFrom(value2)
gtest.Assert(array1.PopLefts(2), []interface{}{0, 1})
gtest.Assert(array1.Slice(), []interface{}{2, 3, 4, 5, 6})
gtest.Assert(array1.PopRights(2), []interface{}{5, 6})
gtest.Assert(array1.Slice(), []interface{}{2, 3, 4})
gtest.Assert(array1.PopRights(20), []interface{}{2, 3, 4})
gtest.Assert(array1.Slice(), []interface{}{})
gtest.Assert(array2.PopLefts(20), []interface{}{0, 1, 2, 3, 4, 5, 6})
gtest.Assert(array2.Slice(), []interface{}{})
})
}
func TestArray_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(value1)
gtest.Assert(array1.Range(0, 1), []interface{}{0})
gtest.Assert(array1.Range(1, 2), []interface{}{1})
gtest.Assert(array1.Range(0, 2), []interface{}{0, 1})
gtest.Assert(array1.Range(-1, 10), value1)
})
}
func TestArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3}
a2 := []interface{}{4, 5, 6, 7}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a2)
gtest.Assert(array1.Merge(array2).Slice(), []interface{}{0, 1, 2, 3, 4, 5, 6, 7})
})
}
func TestArray_Fill(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0}
a2 := []interface{}{0}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a2)
gtest.Assert(array1.Fill(1, 2, 100).Slice(), []interface{}{0, 100, 100})
gtest.Assert(array2.Fill(0, 2, 100).Slice(), []interface{}{100, 100})
})
}
func TestArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{1, 2, 3, 4, 5}
array1 := garray.NewArrayFrom(a1)
chunks := array1.Chunk(2)
gtest.Assert(len(chunks), 3)
gtest.Assert(chunks[0], []interface{}{1, 2})
gtest.Assert(chunks[1], []interface{}{3, 4})
gtest.Assert(chunks[2], []interface{}{5})
})
}
func TestArray_Pad(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Pad(3, 1).Slice(), []interface{}{0, 1, 1})
gtest.Assert(array1.Pad(-4, 1).Slice(), []interface{}{1, 0, 1, 1})
gtest.Assert(array1.Pad(3, 1).Slice(), []interface{}{1, 0, 1, 1})
})
}
func TestArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.SubSlice(0, 2), []interface{}{0, 1})
gtest.Assert(array1.SubSlice(2, 2), []interface{}{2, 3})
gtest.Assert(array1.SubSlice(5, 8), []interface{}{5, 6})
})
}
func TestArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(len(array1.Rands(2)), 2)
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
})
}
func TestArray_Shuffle(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Shuffle().Len(), 7)
})
}
func TestArray_Reverse(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Reverse().Slice(), []interface{}{6, 5, 4, 3, 2, 1, 0})
})
}
func TestArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
})
}
func TestArray_Replace(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
a2 := []interface{}{"a", "b", "c"}
a3 := []interface{}{"m", "n", "p", "z", "x", "y", "d", "u"}
array1 := garray.NewArrayFrom(a1)
array2 := array1.Replace(a2)
gtest.Assert(array2.Len(), 7)
gtest.Assert(array2.Contains("b"), true)
gtest.Assert(array2.Contains(4), true)
gtest.Assert(array2.Contains("v"), false)
array3 := array1.Replace(a3)
gtest.Assert(array3.Len(), 7)
gtest.Assert(array3.Contains(4), false)
gtest.Assert(array3.Contains("p"), true)
gtest.Assert(array3.Contains("u"), false)
})
}
func TestArray_SetArray(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
a2 := []interface{}{"a", "b", "c"}
array1 := garray.NewArrayFrom(a1)
array1 = array1.SetArray(a2)
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1.Contains("b"), true)
gtest.Assert(array1.Contains("5"), false)
})
}
func TestArray_Sum(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3}
a2 := []interface{}{"a", "b", "c"}
a3 := []interface{}{"a", "1", "2"}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a2)
array3 := garray.NewArrayFrom(a3)
gtest.Assert(array1.Sum(), 6)
gtest.Assert(array2.Sum(), 0)
gtest.Assert(array3.Sum(), 3)
})
}
func TestArray_Clone(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3}
array1 := garray.NewArrayFrom(a1)
array2 := array1.Clone()
gtest.Assert(array1.Len(), 4)
gtest.Assert(array2.Sum(), 6)
gtest.AssertEQ(array1, array2)
})
}
func TestArray_CountValues(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "b", "c", "d", "e", "d"}
array1 := garray.NewArrayFrom(a1)
array2 := array1.CountValues()
gtest.Assert(len(array2), 5)
gtest.Assert(array2["b"], 1)
gtest.Assert(array2["d"], 2)
})
}
func TestSortedArray_NewSortedArrayFrom(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "f", "c"}
a2 := []interface{}{"h", "j", "i", "k"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
func2 := func(v1, v2 interface{}) int {
return -1
}
array1 := garray.NewSortedArrayFrom(a1, func1)
array2 := garray.NewSortedArrayFrom(a2, func2)
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1, []interface{}{"a", "c", "f"})
gtest.Assert(array2.Len(), 4)
gtest.Assert(array2, []interface{}{"k", "i", "j", "h"})
})
}
func TestNewSortedArrayFromCopy(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "f", "c"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
func2 := func(v1, v2 interface{}) int {
return -1
}
array1 := garray.NewSortedArrayFromCopy(a1, func1)
array2 := garray.NewSortedArrayFromCopy(a1, func2)
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1, []interface{}{"a", "c", "f"})
gtest.Assert(array1.Len(), 3)
gtest.Assert(array2, []interface{}{"c", "f", "a"})
})
}
func TestSortedArray_SetArray(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "f", "c"}
a2 := []interface{}{"e", "h", "g", "k"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
array1.SetArray(a2)
gtest.Assert(array1.Len(), 4)
gtest.Assert(array1, []interface{}{"e", "g", "h", "k"})
})
}
func TestSortedArray_Sort(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "f", "c"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
array1.Sort()
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1, []interface{}{"a", "c", "f"})
})
}
func TestSortedArray_Get(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "f", "c"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
gtest.Assert(array1.Get(2), "f")
gtest.Assert(array1.Get(1), "c")
})
}
func TestSortedArray_Remove(t *testing.T) {
gtest.Case(t, func() {
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.Remove(1)
gtest.Assert(gconv.String(i1), "b")
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1.Contains("b"), false)
i2 := array1.Remove(0)
gtest.Assert(gconv.String(i2), "a")
gtest.Assert(array1.Len(), 2)
gtest.Assert(array1.Contains("a"), false)
i3 := array1.Remove(1)
gtest.Assert(gconv.String(i3), "d")
gtest.Assert(array1.Len(), 1)
gtest.Assert(array1.Contains("d"), false)
})
}
func TestSortedArray_PopLeft(t *testing.T) {
gtest.Case(t, func() {
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()
gtest.Assert(gconv.String(i1), "a")
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1, []interface{}{"b", "c", "d"})
})
}
func TestSortedArray_PopRight(t *testing.T) {
gtest.Case(t, func() {
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()
gtest.Assert(gconv.String(i1), "d")
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1, []interface{}{"a", "b", "c"})
})
}
func TestSortedArray_PopRand(t *testing.T) {
gtest.Case(t, func() {
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.PopRand()
gtest.AssertIN(i1, []interface{}{"a", "d", "c", "b"})
gtest.Assert(array1.Len(), 3)
})
}
func TestSortedArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
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.PopRands(2)
gtest.Assert(len(i1), 2)
gtest.AssertIN(i1, []interface{}{"a", "d", "c", "b"})
gtest.Assert(array1.Len(), 2)
i2 := array1.PopRands(3)
gtest.Assert(len(i1), 2)
gtest.AssertIN(i2, []interface{}{"a", "d", "c", "b"})
gtest.Assert(array1.Len(), 0)
})
}
func TestSortedArray_PopLefts(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.PopLefts(2)
gtest.Assert(len(i1), 2)
gtest.AssertIN(i1, []interface{}{"a", "d", "c", "b", "e", "f"})
gtest.Assert(array1.Len(), 4)
i2 := array1.PopLefts(5)
gtest.Assert(len(i2), 4)
gtest.AssertIN(i1, []interface{}{"a", "d", "c", "b", "e", "f"})
gtest.Assert(array1.Len(), 0)
})
}
func TestSortedArray_PopRights(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.PopRights(2)
gtest.Assert(len(i1), 2)
gtest.Assert(i1, []interface{}{"e", "f"})
gtest.Assert(array1.Len(), 4)
i2 := array1.PopRights(10)
gtest.Assert(len(i2), 4)
})
}
func TestSortedArray_Range(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.Range(2, 5)
gtest.Assert(i1, []interface{}{"c", "d", "e"})
gtest.Assert(array1.Len(), 6)
i2 := array1.Range(7, 5)
gtest.Assert(len(i2), 0)
i2 = array1.Range(-1, 2)
gtest.Assert(i2, []interface{}{"a", "b"})
i2 = array1.Range(4, 10)
gtest.Assert(len(i2), 2)
gtest.Assert(i2, []interface{}{"e", "f"})
})
}
func TestSortedArray_Sum(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
a2 := []interface{}{"1", "2", "3", "b", "e", "f"}
a3 := []interface{}{"4", "5", "6"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
array2 := garray.NewSortedArrayFrom(a2, func1)
array3 := garray.NewSortedArrayFrom(a3, func1)
gtest.Assert(array1.Sum(), 0)
gtest.Assert(array2.Sum(), 6)
gtest.Assert(array3.Sum(), 15)
})
}
func TestSortedArray_Clone(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
array2 := array1.Clone()
gtest.Assert(array1, array2)
array1.Remove(1)
gtest.AssertNE(array1, array2)
})
}
func TestSortedArray_Clear(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "b", "e", "f"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
gtest.Assert(array1.Len(), 6)
array1.Clear()
gtest.Assert(array1.Len(), 0)
})
}
func TestSortedArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "b", "e"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.Chunk(2)
gtest.Assert(len(i1), 3)
gtest.Assert(i1[0], []interface{}{"a", "b"})
gtest.Assert(i1[2], []interface{}{"e"})
i1 = array1.Chunk(0)
gtest.Assert(len(i1), 0)
})
}
func TestSortedArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "b", "e"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.SubSlice(2, 3)
gtest.Assert(len(i1), 3)
gtest.Assert(i1, []interface{}{"c", "d", "e"})
i1 = array1.SubSlice(2, 6)
gtest.Assert(len(i1), 3)
gtest.Assert(i1, []interface{}{"c", "d", "e"})
i1 = array1.SubSlice(7, 2)
gtest.Assert(len(i1), 0)
})
}
func TestSortedArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.Rand()
gtest.AssertIN(i1, []interface{}{"a", "d", "c"})
gtest.Assert(array1.Len(), 3)
})
}
func TestSortedArray_Rands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.Rands(2)
gtest.AssertIN(i1, []interface{}{"a", "d", "c"})
gtest.Assert(len(i1), 2)
gtest.Assert(array1.Len(), 3)
i1 = array1.Rands(4)
gtest.Assert(len(i1), 3)
})
}
func TestSortedArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
gtest.Assert(array1.Join(","), "a,c,d")
gtest.Assert(array1.Join("."), "a.c.d")
})
}
func TestSortedArray_CountValues(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "c"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
m1 := array1.CountValues()
gtest.Assert(len(m1), 3)
gtest.Assert(m1["c"], 2)
gtest.Assert(m1["a"], 1)
})
}
func TestSortedArray_SetUnique(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"a", "d", "c", "c"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
array1.SetUnique(true)
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1, []interface{}{"a", "c", "d"})
})
}

View File

@ -0,0 +1,639 @@
// 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.
// go test *.go
package garray_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"strings"
"testing"
)
func Test_StringArray_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []string{"0", "1", "2", "3"}
array := garray.NewStringArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
array.Set(0, "100")
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
gtest.Assert(array.Search("100"), 0)
gtest.Assert(array.Contains("100"), true)
gtest.Assert(array.Remove(0), 100)
gtest.Assert(array.Contains("100"), false)
array.Append("4")
gtest.Assert(array.Len(), 4)
array.InsertBefore(0, "100")
array.InsertAfter(0, "200")
gtest.Assert(array.Slice(), []string{"100", "200", "1", "2", "3", "4"})
array.InsertBefore(5, "300")
array.InsertAfter(6, "400")
gtest.Assert(array.Slice(), []string{"100", "200", "1", "2", "3", "300", "4", "400"})
gtest.Assert(array.Clear().Len(), 0)
})
}
func TestStringArray_Sort(t *testing.T) {
gtest.Case(t, func() {
expect1 := []string{"0", "1", "2", "3"}
expect2 := []string{"3", "2", "1", "0"}
array := garray.NewStringArray()
for i := 3; i >= 0; i-- {
array.Append(gconv.String(i))
}
array.Sort()
gtest.Assert(array.Slice(), expect1)
array.Sort(true)
gtest.Assert(array.Slice(), expect2)
})
}
func TestStringArray_Unique(t *testing.T) {
gtest.Case(t, func() {
expect := []string{"1", "1", "2", "3"}
array := garray.NewStringArrayFrom(expect)
gtest.Assert(array.Unique().Slice(), []string{"1", "2", "3"})
})
}
func TestStringArray_PushAndPop(t *testing.T) {
gtest.Case(t, func() {
expect := []string{"0", "1", "2", "3"}
array := garray.NewStringArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.PopLeft(), "0")
gtest.Assert(array.PopRight(), "3")
gtest.AssertIN(array.PopRand(), []string{"1", "2"})
gtest.AssertIN(array.PopRand(), []string{"1", "2"})
gtest.Assert(array.Len(), 0)
array.PushLeft("1").PushRight("2")
gtest.Assert(array.Slice(), []string{"1", "2"})
})
}
func TestStringArray_PopLeftsAndPopRights(t *testing.T) {
gtest.Case(t, func() {
value1 := []string{"0", "1", "2", "3", "4", "5", "6"}
value2 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(value1)
array2 := garray.NewStringArrayFrom(value2)
gtest.Assert(array1.PopLefts(2), []interface{}{"0", "1"})
gtest.Assert(array1.Slice(), []interface{}{"2", "3", "4", "5", "6"})
gtest.Assert(array1.PopRights(2), []interface{}{"5", "6"})
gtest.Assert(array1.Slice(), []interface{}{"2", "3", "4"})
gtest.Assert(array1.PopRights(20), []interface{}{"2", "3", "4"})
gtest.Assert(array1.Slice(), []interface{}{})
gtest.Assert(array2.PopLefts(20), []interface{}{"0", "1", "2", "3", "4", "5", "6"})
gtest.Assert(array2.Slice(), []interface{}{})
})
}
func TestString_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(value1)
gtest.Assert(array1.Range(0, 1), []interface{}{"0"})
gtest.Assert(array1.Range(1, 2), []interface{}{"1"})
gtest.Assert(array1.Range(0, 2), []interface{}{"0", "1"})
gtest.Assert(array1.Range(-1, 10), value1)
})
}
func TestStringArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3"}
a2 := []string{"4", "5", "6", "7"}
array1 := garray.NewStringArrayFrom(a1)
array2 := garray.NewStringArrayFrom(a2)
gtest.Assert(array1.Merge(array2).Slice(), []string{"0", "1", "2", "3", "4", "5", "6", "7"})
})
}
func TestStringArray_Fill(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0"}
a2 := []string{"0"}
array1 := garray.NewStringArrayFrom(a1)
array2 := garray.NewStringArrayFrom(a2)
gtest.Assert(array1.Fill(1, 2, "100").Slice(), []string{"0", "100", "100"})
gtest.Assert(array2.Fill(0, 2, "100").Slice(), []string{"100", "100"})
s1 := array2.Fill(-1, 2, "100")
gtest.Assert(s1.Len(), 2)
})
}
func TestStringArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"1", "2", "3", "4", "5"}
array1 := garray.NewStringArrayFrom(a1)
chunks := array1.Chunk(2)
gtest.Assert(len(chunks), 3)
gtest.Assert(chunks[0], []string{"1", "2"})
gtest.Assert(chunks[1], []string{"3", "4"})
gtest.Assert(chunks[2], []string{"5"})
gtest.Assert(len(array1.Chunk(0)), 0)
})
}
func TestStringArray_Pad(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Pad(3, "1").Slice(), []string{"0", "1", "1"})
gtest.Assert(array1.Pad(-4, "1").Slice(), []string{"1", "0", "1", "1"})
gtest.Assert(array1.Pad(3, "1").Slice(), []string{"1", "0", "1", "1"})
})
}
func TestStringArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.SubSlice(0, 2), []string{"0", "1"})
gtest.Assert(array1.SubSlice(2, 2), []string{"2", "3"})
gtest.Assert(array1.SubSlice(5, 8), []string{"5", "6"})
})
}
func TestStringArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(len(array1.Rands(2)), "2")
gtest.Assert(len(array1.Rands(10)), "7")
gtest.AssertIN(array1.Rands(1)[0], a1)
gtest.Assert(len(array1.Rand()), 1)
gtest.AssertIN(array1.Rand(), a1)
})
}
func TestStringArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"a", "b", "c", "d", "e", "f", "g"}
a2 := []string{"1", "2", "3", "4", "5", "6", "7"}
array1 := garray.NewStringArrayFrom(a1)
//todo gtest.AssertIN(array1.PopRands(1),a1)
gtest.AssertIN(array1.PopRands(1), strings.Join(a1, ","))
gtest.AssertNI(array1.PopRands(1), strings.Join(a2, ","))
})
}
func TestStringArray_Shuffle(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Shuffle().Len(), 7)
})
}
func TestStringArray_Reverse(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Reverse().Slice(), []string{"6", "5", "4", "3", "2", "1", "0"})
})
}
func TestStringArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
})
}
func TestNewStringArrayFromCopy(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
a2 := garray.NewStringArrayFromCopy(a1)
a3 := garray.NewStringArrayFromCopy(a1, true)
gtest.Assert(a2.Contains("1"), true)
gtest.Assert(a2.Len(), 7)
gtest.Assert(a2, a3)
})
}
func TestStringArray_SetArray(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
a2 := []string{"a", "b", "c", "d"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Contains("2"), true)
gtest.Assert(array1.Len(), 7)
array1 = array1.SetArray(a2)
gtest.Assert(array1.Contains("2"), false)
gtest.Assert(array1.Contains("c"), true)
gtest.Assert(array1.Len(), 4)
})
}
func TestStringArray_Replace(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
a2 := []string{"a", "b", "c", "d"}
a3 := []string{"o", "p", "q", "x", "y", "z", "w", "r", "v"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Contains("2"), true)
gtest.Assert(array1.Len(), 7)
array1 = array1.Replace(a2)
gtest.Assert(array1.Contains("2"), false)
gtest.Assert(array1.Contains("c"), true)
gtest.Assert(array1.Contains("5"), true)
gtest.Assert(array1.Len(), 7)
array1 = array1.Replace(a3)
gtest.Assert(array1.Contains("2"), false)
gtest.Assert(array1.Contains("c"), false)
gtest.Assert(array1.Contains("5"), false)
gtest.Assert(array1.Contains("p"), true)
gtest.Assert(array1.Contains("r"), false)
gtest.Assert(array1.Len(), 7)
})
}
func TestStringArray_Sum(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
a2 := []string{"0", "a", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
array2 := garray.NewStringArrayFrom(a2)
gtest.Assert(array1.Sum(), 21)
gtest.Assert(array2.Sum(), 18)
})
}
//func TestStringArray_SortFunc(t *testing.T) {
// gtest.Case(t, func() {
// a1 := []string{"0","1","2","3","4","5","6"}
// //a2 := []string{"0","a","3","4","5","6"}
// array1 := garray.NewStringArrayFrom(a1)
//
// lesss:=func(v1,v2 string)bool{
// if v1>v2{
// return true
// }
// return false
// }
// gtest.Assert(array1.Len(),7)
// gtest.Assert(lesss("1","2"),false)
// gtest.Assert(array1.SortFunc(lesss("1","2")) ,false)
//
//
// })
//}
func TestStringArray_PopRand(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
str1 := array1.PopRand()
gtest.Assert(strings.Contains("0,1,2,3,4,5,6", str1), true)
gtest.Assert(array1.Len(), 6)
})
}
func TestStringArray_Clone(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStringArrayFrom(a1)
array2 := array1.Clone()
gtest.Assert(array2, array1)
gtest.Assert(array2.Len(), 7)
})
}
func TestStringArray_CountValues(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3", "4", "4", "6"}
array1 := garray.NewStringArrayFrom(a1)
m1 := array1.CountValues()
gtest.Assert(len(m1), 6)
gtest.Assert(m1["2"], 1)
gtest.Assert(m1["4"], 2)
})
}
func TestNewSortedStringArrayFrom(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"a", "d", "c", "b"}
s1 := garray.NewSortedStringArrayFrom(a1, true)
gtest.Assert(s1, []string{"a", "b", "c", "d"})
s2 := garray.NewSortedStringArrayFrom(a1, false)
gtest.Assert(s2, []string{"a", "b", "c", "d"})
})
}
func TestNewSortedStringArrayFromCopy(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"a", "d", "c", "b"}
s1 := garray.NewSortedStringArrayFromCopy(a1, true)
gtest.Assert(s1.Len(), 4)
gtest.Assert(s1, []string{"a", "b", "c", "d"})
})
}
func TestSortedStringArray_SetArray(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"a", "d", "c", "b"}
a2 := []string{"f", "g", "h"}
array1 := garray.NewSortedStringArrayFrom(a1)
array1.SetArray(a2)
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1.Contains("d"), false)
gtest.Assert(array1.Contains("b"), false)
gtest.Assert(array1.Contains("g"), true)
})
}
func TestSortedStringArray_Sort(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"a", "d", "c", "b"}
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.Assert(array1, []string{"a", "b", "c", "d"})
array1.Sort() //todo 这个SortedStringArray.sort这个方法没有必要
gtest.Assert(array1.Len(), 4)
gtest.Assert(array1.Contains("c"), true)
gtest.Assert(array1, []string{"a", "b", "c", "d"})
})
}
func TestSortedStringArray_Get(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"a", "d", "c", "b"}
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.Assert(array1.Get(2), "c")
gtest.Assert(array1.Get(0), "a")
})
}
func TestSortedStringArray_Remove(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"a", "d", "c", "b"}
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.Assert(array1.Remove(2), "c")
gtest.Assert(array1.Get(2), "d")
gtest.Assert(array1.Len(), 3)
gtest.Assert(array1.Contains("c"), false)
gtest.Assert(array1.Remove(0), "a")
gtest.Assert(array1.Len(), 2)
gtest.Assert(array1.Contains("a"), false)
// 此时array1里的元素只剩下2个
gtest.Assert(array1.Remove(1), "d")
gtest.Assert(array1.Len(), 1)
})
}
func TestSortedStringArray_PopLeft(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.PopLeft()
gtest.Assert(s1, "a")
gtest.Assert(array1.Len(), 4)
gtest.Assert(array1.Contains("a"), false)
})
}
func TestSortedStringArray_PopRight(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.PopRight()
gtest.Assert(s1, "e")
gtest.Assert(array1.Len(), 4)
gtest.Assert(array1.Contains("e"), false)
})
}
func TestSortedStringArray_PopRand(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.PopRand()
gtest.AssertIN(s1, []string{"e", "a", "d", "c", "b"})
gtest.Assert(array1.Len(), 4)
gtest.Assert(array1.Contains(s1), false)
})
}
func TestSortedStringArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.PopRands(2)
gtest.AssertIN(s1, []string{"e", "a", "d", "c", "b"})
gtest.Assert(array1.Len(), 3)
gtest.Assert(len(s1), 2)
s1 = array1.PopRands(4)
gtest.Assert(len(s1), 3)
gtest.AssertIN(s1, []string{"e", "a", "d", "c", "b"})
})
}
func TestSortedStringArray_PopLefts(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.PopLefts(2)
gtest.Assert(s1, []string{"a", "b"})
gtest.Assert(array1.Len(), 3)
gtest.Assert(len(s1), 2)
s1 = array1.PopLefts(4)
gtest.Assert(len(s1), 3)
gtest.Assert(s1, []string{"c", "d", "e"})
})
}
func TestSortedStringArray_PopRights(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.PopRights(2)
gtest.Assert(s1, []string{"f", "g"})
gtest.Assert(array1.Len(), 5)
gtest.Assert(len(s1), 2)
s1 = array1.PopRights(6)
gtest.Assert(len(s1), 5)
gtest.Assert(s1, []string{"a", "b", "c", "d", "e"})
gtest.Assert(array1.Len(), 0)
})
}
func TestSortedStringArray_Range(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.Range(2, 4)
gtest.Assert(len(s1), 2)
gtest.Assert(s1, []string{"c", "d"})
s1 = array1.Range(-1, 2)
gtest.Assert(len(s1), 2)
gtest.Assert(s1, []string{"a", "b"})
s1 = array1.Range(4, 8)
gtest.Assert(len(s1), 3)
gtest.Assert(s1, []string{"e", "f", "g"})
})
}
func TestSortedStringArray_Sum(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
a2 := []string{"1", "2", "3", "4", "a"}
array1 := garray.NewSortedStringArrayFrom(a1)
array2 := garray.NewSortedStringArrayFrom(a2)
gtest.Assert(array1.Sum(), 0)
gtest.Assert(array2.Sum(), 10)
})
}
func TestSortedStringArray_Clone(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
array1 := garray.NewSortedStringArrayFrom(a1)
array2 := array1.Clone()
gtest.Assert(array1, array2)
array1.Remove(1)
gtest.Assert(array2.Len(), 7)
})
}
func TestSortedStringArray_Clear(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
array1 := garray.NewSortedStringArrayFrom(a1)
array1.Clear()
gtest.Assert(array1.Len(), 0)
})
}
func TestSortedStringArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.SubSlice(1, 3)
gtest.Assert(len(s1), 3)
gtest.Assert(s1, []string{"b", "c", "d"})
gtest.Assert(array1.Len(), 7)
s2 := array1.SubSlice(1, 10)
gtest.Assert(len(s2), 6)
s3 := array1.SubSlice(10, 2)
gtest.Assert(len(s3), 0)
})
}
func TestSortedStringArray_Len(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "c", "b", "f", "g"}
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.Assert(array1.Len(), 7)
})
}
func TestSortedStringArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d"}
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.AssertIN(array1.Rand(), []string{"e", "a", "d"})
})
}
func TestSortedStringArray_Rands(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d"}
array1 := garray.NewSortedStringArrayFrom(a1)
s1 := array1.Rands(2)
gtest.AssertIN(s1, []string{"e", "a", "d"})
gtest.Assert(len(s1), 2)
s1 = array1.Rands(4)
gtest.AssertIN(s1, []string{"e", "a", "d"})
gtest.Assert(len(s1), 3)
})
}
func TestSortedStringArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d"}
array1 := garray.NewSortedStringArrayFrom(a1)
gtest.Assert(array1.Join(","), "a,d,e")
gtest.Assert(array1.Join("."), "a.d.e")
})
}
func TestSortedStringArray_CountValues(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "a", "c"}
array1 := garray.NewSortedStringArrayFrom(a1)
m1 := array1.CountValues()
gtest.Assert(m1["a"], 2)
gtest.Assert(m1["d"], 1)
})
}
func TestSortedStringArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "a", "c"}
array1 := garray.NewSortedStringArrayFrom(a1)
array2 := array1.Chunk(2)
gtest.Assert(len(array2), 3)
gtest.Assert(len(array2[0]), 2)
gtest.Assert(array2[1], []string{"c", "d"})
})
}
func TestSortedStringArray_SetUnique(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "a", "c"}
array1 := garray.NewSortedStringArrayFrom(a1)
array2 := array1.SetUnique(true)
gtest.Assert(array2.Len(), 4)
gtest.Assert(array2, []string{"a", "c", "d", "e"})
})
}
func TestStringArray_Remove(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"e", "a", "d", "a", "c"}
array1 := garray.NewStringArrayFrom(a1)
s1 := array1.Remove(1)
gtest.Assert(s1, "a")
gtest.Assert(array1.Len(), 4)
s1 = array1.Remove(3)
gtest.Assert(s1, "c")
gtest.Assert(array1.Len(), 3)
})
}

View File

@ -1,84 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go
package garray_test
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/util/gtest"
"strings"
"testing"
)
func Test_IntArray_Unique(t *testing.T) {
expect := []int{1, 2, 3, 4, 5, 6}
array := garray.NewIntArray(0, 0)
array.Append(1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6)
array.Unique()
gtest.Assert(array.Slice(), expect)
}
func Test_SortedIntArray1(t *testing.T) {
expect := []int{0,1,2,3,4,5,6,7,8,9,10}
array := garray.NewSortedIntArray(0)
for i := 10; i > -1; i-- {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedIntArray2(t *testing.T) {
expect := []int{0,1,2,3,4,5,6,7,8,9,10}
array := garray.NewSortedIntArray(0)
for i := 0; i <= 10; i++ {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedStringArray1(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedStringArray(0)
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedStringArray2(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedStringArray(0)
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray1(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedArray(0, func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray2(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedArray(0, func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}

View File

@ -1,53 +1,70 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// Package gchan provides graceful operations for channel.
// Package gchan provides graceful channel for no panic operations.
//
// 优雅的Channel操作.
// It's safe to call Chan.Push/Close functions repeatedly.
package gchan
import (
"errors"
"gitee.com/johng/gf/g/container/gtype"
"errors"
"github.com/gogf/gf/g/container/gtype"
)
// Graceful channel.
type Chan struct {
list chan interface{}
closed *gtype.Bool
channel chan interface{}
closed *gtype.Bool
}
// New creates a graceful channel with given <limit>.
func New(limit int) *Chan {
return &Chan {
list : make(chan interface{}, limit),
closed : gtype.NewBool(),
}
return &Chan{
channel: make(chan interface{}, limit),
closed: gtype.NewBool(),
}
}
// 将数据压入队列
func (q *Chan) Push(v interface{}) error {
if q.closed.Val() {
return errors.New("closed")
}
q.list <- v
return nil
// 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
}
// 先进先出地从队列取出一项数据,当没有数据可获取时,阻塞等待
func (q *Chan) Pop() interface{} {
return <- q.list
// 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
}
// 关闭队列(通知所有通过Pop阻塞的协程退出)
func (q *Chan) Close() {
if !q.closed.Set(true) {
close(q.list)
}
// Close closes the channel.
// It is safe to be called repeatedly.
func (c *Chan) Close() {
if !c.closed.Set(true) {
close(c.channel)
}
}
// 获取当前队列大小
func (q *Chan) Size() int {
return len(q.list)
}
// 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

@ -0,0 +1,29 @@
// 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/g/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,32 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*"
package gchan_test
import (
"testing"
"gitee.com/johng/gf/g/container/gchan"
)
var length = 10000000
var q1 = gchan.New(length)
var q2 = make(chan int, length)
func BenchmarkGchanPushAndPop(b *testing.B) {
for i := 0; i < b.N; i++ {
q1.Push(i)
q1.Pop()
}
}
func BenchmarkChannelPushAndPop(b *testing.B) {
for i := 0; i < b.N; i++ {
q2 <- i
<- q2
}
}

View File

@ -0,0 +1,47 @@
package gchan_test
import (
"errors"
"testing"
"github.com/gogf/gf/g/container/gchan"
"github.com/gogf/gf/g/test/gtest"
)
func Test_Gchan(t *testing.T) {
gtest.Case(t, func() {
ch := gchan.New(10)
gtest.Assert(ch.Cap(), 10)
gtest.Assert(ch.Push(1), nil)
gtest.Assert(ch.Len(), 1)
gtest.Assert(ch.Size(), 1)
ch.Pop()
gtest.Assert(ch.Len(), 0)
gtest.Assert(ch.Size(), 0)
ch.Close()
gtest.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
}
gtest.Assert(v, i)
i++
}
}()
for index := 0; index < 10; index++ {
ch.Push(index)
}
ch.Close()
gtest.Assert(ch1.Pop(), 10)
ch1.Close()
})
}

View File

@ -1,306 +1,372 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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 l file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
//
// Package glist provides a concurrent-safe(alternative) doubly linked list.
//
// 并发安全的双向链表.
// Package glist provides a concurrent-safe/unsafe doubly linked list.
package glist
import (
"container/list"
"gitee.com/johng/gf/g/internal/rwmutex"
"container/list"
"github.com/gogf/gf/g/internal/rwmutex"
)
// 变长双向链表
type List struct {
mu *rwmutex.RWMutex
list *list.List
type (
List struct {
mu *rwmutex.RWMutex
list *list.List
}
Element = list.Element
)
// New creates and returns a new empty doubly linked list.
func New(unsafe ...bool) *List {
return &List{
mu: rwmutex.New(unsafe...),
list: list.New(),
}
}
type Element = list.Element
// 获得一个变长链表指针
func New(unsafe...bool) *List {
return &List {
mu : rwmutex.New(unsafe...),
list : list.New(),
}
}
// 往链表头入栈数据项
// 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()
e = l.list.PushFront(v)
l.mu.Unlock()
return
}
// 往链表尾入栈数据项
func (l *List) PushBack(v interface{}) (e *Element) {
l.mu.Lock()
e = l.list.PushBack(v)
l.mu.Unlock()
return
}
// 批量往链表头入栈数据项
func (l *List) BatchPushFront(values []interface{}) {
l.mu.Lock()
for _, v := range values {
l.list.PushFront(v)
}
l.mu.Unlock()
}
// 批量往链表尾入栈数据项
func (l *List) BatchPushBack(values []interface{}) {
l.mu.Lock()
for _, v := range values {
l.list.PushBack(v)
}
l.mu.Unlock()
}
// 从链表尾端出栈数据项(删除)
func (l *List) PopBack() (value interface{}) {
l.mu.Lock()
if e := l.list.Back(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
l.mu.Lock()
e = l.list.PushFront(v)
l.mu.Unlock()
return
}
// 从链表头端出栈数据项(删除)
// 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()
e = l.list.PushBack(v)
l.mu.Unlock()
return
}
// PushFronts inserts multiple new elements with values <values> at the front of list <l>.
func (l *List) PushFronts(values []interface{}) {
l.mu.Lock()
for _, v := range values {
l.list.PushFront(v)
}
l.mu.Unlock()
}
// PushBacks inserts multiple new elements with values <values> at the back of list <l>.
func (l *List) PushBacks(values []interface{}) {
l.mu.Lock()
for _, v := range values {
l.list.PushBack(v)
}
l.mu.Unlock()
}
// PopBack removes the element from back of <l> and returns the value of the element.
func (l *List) PopBack() (value interface{}) {
l.mu.Lock()
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()
if e := l.list.Front(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
return
l.mu.Lock()
if e := l.list.Front(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
return
}
// 批量从链表尾端出栈数据项(删除)
func (l *List) BatchPopBack(max int) (values []interface{}) {
l.mu.Lock()
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
length = max
}
tempe := (*Element)(nil)
values = make([]interface{}, length)
for i := 0; i < length; i++ {
tempe = l.list.Back()
values[i] = l.list.Remove(tempe)
}
}
l.mu.Unlock()
return
}
// 批量从链表头端出栈数据项(删除)
func (l *List) BatchPopFront(max int) (values []interface{}) {
l.mu.RLock()
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
length = max
}
tempe := (*Element)(nil)
values = make([]interface{}, length)
for i := 0; i < length; i++ {
tempe = l.list.Front()
values[i] = l.list.Remove(tempe)
}
}
l.mu.RUnlock()
return
}
// 批量从链表尾端依次获取所有数据(删除)
func (l *List) PopBackAll() []interface{} {
return l.BatchPopBack(-1)
}
// 批量从链表头端依次获取所有数据(删除)
func (l *List) PopFrontAll() []interface{} {
return l.BatchPopFront(-1)
}
// 从链表头获取所有数据(不删除)
func (l *List) FrontAll() (values []interface{}) {
l.mu.RLock()
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
for i, e := 0, l.list.Front(); i < length; i, e = i + 1, e.Next() {
values[i] = e.Value
}
}
l.mu.RUnlock()
return
}
// 从链表尾获取所有数据(不删除)
func (l *List) BackAll() (values []interface{}) {
l.mu.RLock()
// PopBacks removes <max> elements from back of <l>
// and returns values of the removed elements as slice.
func (l *List) PopBacks(max int) (values []interface{}) {
l.mu.Lock()
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
for i, e := 0, l.list.Back(); i < length; i, e = i + 1, e.Prev() {
values[i] = e.Value
}
}
l.mu.RUnlock()
if max > 0 && max < length {
length = max
}
values = make([]interface{}, length)
for i := 0; i < length; i++ {
values[i] = l.list.Remove(l.list.Back())
}
}
l.mu.Unlock()
return
}
// 获取链表头值(不删除)
func (l *List) FrontItem() (value interface{}) {
l.mu.RLock()
if e := l.list.Front(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
// PopFronts removes <max> elements from front of <l>
// and returns values of the removed elements as slice.
func (l *List) PopFronts(max int) (values []interface{}) {
l.mu.Lock()
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
length = max
}
values = make([]interface{}, length)
for i := 0; i < length; i++ {
values[i] = l.list.Remove(l.list.Front())
}
}
l.mu.Unlock()
return
}
// 获取链表尾值(不删除)
func (l *List) BackItem() (value interface{}) {
l.mu.RLock()
if e := l.list.Back(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
// PopBackAll removes all elements from back of <l>
// and returns values of the removed elements as slice.
func (l *List) PopBackAll() []interface{} {
return l.PopBacks(-1)
}
// 获取表头指针
// PopFrontAll removes all elements from front of <l>
// and returns values of the removed elements as slice.
func (l *List) PopFrontAll() []interface{} {
return l.PopFronts(-1)
}
// FrontAll copies and returns values of all elements from front of <l> as slice.
func (l *List) FrontAll() (values []interface{}) {
l.mu.RLock()
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
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()
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
for i, e := 0, l.list.Back(); i < length; i, e = i+1, e.Prev() {
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()
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()
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()
e = l.list.Front()
l.mu.RUnlock()
return
}
// 获取表位指针
func (l *List) Back() (e *Element) {
l.mu.RLock()
e = l.list.Back()
l.mu.RUnlock()
return
}
// 获取链表长度
func (l *List) Len() (length int) {
l.mu.RLock()
length = l.list.Len()
l.mu.RUnlock()
l.mu.RLock()
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()
e = l.list.Back()
l.mu.RUnlock()
return
}
// Len returns the number of elements of list <l>.
// The complexity is O(1).
func (l *List) Len() (length int) {
l.mu.RLock()
length = l.list.Len()
l.mu.RUnlock()
return
}
// Size is alias of Len.
func (l *List) Size() int {
return l.Len()
}
// MoveBefore moves element <e> to its new position before <p>.
// If <e> or <p> is not an element of <l>, or <e> == <p>, the list is not modified.
// The element and <p> must not be nil.
func (l *List) MoveBefore(e, p *Element) {
l.mu.Lock()
l.list.MoveBefore(e, p)
l.mu.Unlock()
l.mu.Lock()
l.list.MoveBefore(e, p)
l.mu.Unlock()
}
// MoveAfter moves element <e> to its new position after <p>.
// If <e> or <p> is not an element of <l>, or <e> == <p>, the list is not modified.
// The element and <p> must not be nil.
func (l *List) MoveAfter(e, p *Element) {
l.mu.Lock()
l.list.MoveAfter(e, p)
l.mu.Unlock()
l.mu.Lock()
l.list.MoveAfter(e, p)
l.mu.Unlock()
}
// MoveToFront moves element <e> to the front of list <l>.
// If <e> is not an element of <l>, the list is not modified.
// The element must not be nil.
func (l *List) MoveToFront(e *Element) {
l.mu.Lock()
l.list.MoveToFront(e)
l.mu.Unlock()
l.mu.Lock()
l.list.MoveToFront(e)
l.mu.Unlock()
}
// MoveToBack moves element <e> to the back of list <l>.
// If <e> is not an element of <l>, the list is not modified.
// The element must not be nil.
func (l *List) MoveToBack(e *Element) {
l.mu.Lock()
l.list.MoveToBack(e)
l.mu.Unlock()
l.mu.Lock()
l.list.MoveToBack(e)
l.mu.Unlock()
}
// PushBackList inserts a copy of an other list at the back of list <l>.
// The lists <l> and <other> may be the same, but they must not be nil.
func (l *List) PushBackList(other *List) {
if l != other {
other.mu.RLock()
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushBackList(other.list)
l.mu.Unlock()
if l != other {
other.mu.RLock()
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushBackList(other.list)
l.mu.Unlock()
}
// PushFrontList inserts a copy of an other list at the front of list <l>.
// The lists <l> and <other> may be the same, but they must not be nil.
func (l *List) PushFrontList(other *List) {
if l != other {
other.mu.RLock()
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushFrontList(other.list)
l.mu.Unlock()
if l != other {
other.mu.RLock()
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushFrontList(other.list)
l.mu.Unlock()
}
// 在list中元素项p之后插入一个值为v的元素并返回该元素如果mark不是list中元素则list不改变。
// InsertAfter inserts a new element <e> with value <v> immediately after <p> and returns <e>.
// If <p> is not an element of <l>, the list is not modified.
// The <p> must not be nil.
func (l *List) InsertAfter(v interface{}, p *Element) (e *Element) {
l.mu.Lock()
e = l.list.InsertAfter(v, p)
l.mu.Unlock()
return
l.mu.Lock()
e = l.list.InsertAfter(v, p)
l.mu.Unlock()
return
}
// 在list中元素项p之前插入一个值为v的元素并返回该元素如果mark不是list中元素则list不改变。
// InsertBefore inserts a new element <e> with value <v> immediately before <p> and returns <e>.
// If <p> is not an element of <l>, the list is not modified.
// The <p> must not be nil.
func (l *List) InsertBefore(v interface{}, p *Element) (e *Element) {
l.mu.Lock()
e = l.list.InsertBefore(v, p)
l.mu.Unlock()
return
l.mu.Lock()
e = l.list.InsertBefore(v, p)
l.mu.Unlock()
return
}
// 删除数据项e, 并返回删除项的元素项
// Remove removes <e> from <l> if <e> is an element of list <l>.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List) Remove(e *Element) (value interface{}) {
l.mu.Lock()
value = l.list.Remove(e)
l.mu.Unlock()
return
l.mu.Lock()
value = l.list.Remove(e)
l.mu.Unlock()
return
}
// 批量删除数据项
func (l *List) BatchRemove(es []*Element) {
l.mu.Lock()
for _, e := range es {
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()
for _, e := range es {
l.list.Remove(e)
}
l.mu.Unlock()
return
}
// 删除所有数据项
// RemoveAll removes all elements from list <l>.
func (l *List) RemoveAll() {
l.mu.Lock()
l.list = list.New()
l.mu.Unlock()
l.mu.Lock()
l.list = list.New()
l.mu.Unlock()
}
// 读锁操作
// See RemoveAll().
func (l *List) Clear() {
l.RemoveAll()
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (l *List) RLockFunc(f func(list *list.List)) {
l.mu.RLock()
defer l.mu.RUnlock()
f(l.list)
l.mu.RLock()
defer l.mu.RUnlock()
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()
f(l.list)
}
l.mu.Lock()
defer l.mu.Unlock()
f(l.list)
}
// Iterator is alias of IteratorAsc.
func (l *List) Iterator(f func(e *Element) bool) {
l.IteratorAsc(f)
}
// IteratorAsc iterates the list 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()
length := l.list.Len()
if length > 0 {
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
if !f(e) {
break
}
}
}
l.mu.RUnlock()
}
// IteratorDesc iterates the list 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()
length := l.list.Len()
if length > 0 {
for i, e := 0, l.list.Back(); i < length; i, e = i+1, e.Prev() {
if !f(e) {
break
}
}
}
l.mu.RUnlock()
}

View File

@ -0,0 +1,36 @@
// 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 glist_test
import (
"fmt"
"github.com/gogf/gf/g/container/glist"
)
func Example_basic() {
n := 10
l := glist.New()
for i := 0; i < n; i++ {
l.PushBack(i)
}
fmt.Println(l.Len())
fmt.Println(l.FrontAll())
fmt.Println(l.BackAll())
for i := 0; i < n; i++ {
fmt.Print(l.PopFront())
}
l.Clear()
fmt.Println()
fmt.Println(l.Len())
// Output:
//10
//[0 1 2 3 4 5 6 7 8 9]
//[9 8 7 6 5 4 3 2 1 0]
//0123456789
//0
}

View File

@ -1,56 +1,53 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package glist
import (
"testing"
"testing"
)
var (
l = New()
bn = 20000000
l = New()
bn = 20000000
)
func Benchmark_PushBack(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PushBack(i)
}
b.N = bn
for i := 0; i < b.N; i++ {
l.PushBack(i)
}
}
func Benchmark_PushFront(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PushFront(i)
}
b.N = bn
for i := 0; i < b.N; i++ {
l.PushFront(i)
}
}
func Benchmark_Len(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.Len()
}
b.N = bn
for i := 0; i < b.N; i++ {
l.Len()
}
}
func Benchmark_PopFront(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PopFront()
}
b.N = bn
for i := 0; i < b.N; i++ {
l.PopFront()
}
}
func Benchmark_PopBack(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PopBack()
}
b.N = bn
for i := 0; i < b.N; i++ {
l.PopBack()
}
}

View File

@ -1,367 +1,583 @@
// Copyright 2019 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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 this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package glist
import (
"container/list"
"testing"
"container/list"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
)
// 检查链表长度
func checkListLen(t *testing.T, l *List, len int) bool {
if n := l.Len(); n != len {
t.Errorf("l.Len() = %d, want %d", n, len)
return false
}
return true
if n := l.Len(); n != len {
t.Errorf("l.Len() = %d, want %d", n, len)
return false
}
return true
}
// 检查指针地址
func checkListPointers(t *testing.T, l *List, es []*Element) {
if !checkListLen(t, l, len(es)) {
return
}
l.RLockFunc(func(list *list.List) {
for i, e := 0, l.list.Front(); i < list.Len(); i, e = i + 1, e.Next() {
if e.Prev() != es[i].Prev() {
t.Errorf("list[%d].Prev = %p, want %p", i, e.Prev(), es[i].Prev())
}
if e.Next() != es[i].Next() {
t.Errorf("list[%d].Next = %p, want %p", i, e.Next(), es[i].Next())
}
}
})
if !checkListLen(t, l, len(es)) {
return
}
l.RLockFunc(func(list *list.List) {
for i, e := 0, l.list.Front(); i < list.Len(); i, e = i+1, e.Next() {
if e.Prev() != es[i].Prev() {
t.Errorf("list[%d].Prev = %p, want %p", i, e.Prev(), es[i].Prev())
}
if e.Next() != es[i].Next() {
t.Errorf("list[%d].Next = %p, want %p", i, e.Next(), es[i].Next())
}
}
})
}
func TestBasic(t *testing.T) {
l := New()
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)
}
l := New()
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 TestList(t *testing.T) {
l := New()
checkListPointers(t, l, []*Element{})
l := New()
checkListPointers(t, l, []*Element{})
// Single element list
e := l.PushFront("a")
checkListPointers(t, l, []*Element{e})
l.MoveToFront(e)
checkListPointers(t, l, []*Element{e})
l.MoveToBack(e)
checkListPointers(t, l, []*Element{e})
l.Remove(e)
checkListPointers(t, l, []*Element{})
// Single element list
e := l.PushFront("a")
checkListPointers(t, l, []*Element{e})
l.MoveToFront(e)
checkListPointers(t, l, []*Element{e})
l.MoveToBack(e)
checkListPointers(t, l, []*Element{e})
l.Remove(e)
checkListPointers(t, l, []*Element{})
// Bigger list
e2 := l.PushFront(2)
e1 := l.PushFront(1)
e3 := l.PushBack(3)
e4 := l.PushBack("banana")
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
// Bigger list
e2 := l.PushFront(2)
e1 := l.PushFront(1)
e3 := l.PushBack(3)
e4 := l.PushBack("banana")
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.Remove(e2)
checkListPointers(t, l, []*Element{e1, e3, e4})
l.Remove(e2)
checkListPointers(t, l, []*Element{e1, e3, e4})
l.MoveToFront(e3) // move from middle
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToFront(e3) // move from middle
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToFront(e1)
l.MoveToBack(e3) // move from middle
checkListPointers(t, l, []*Element{e1, e4, e3})
l.MoveToFront(e1)
l.MoveToBack(e3) // move from middle
checkListPointers(t, l, []*Element{e1, e4, e3})
l.MoveToFront(e3) // move from back
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToFront(e3) // should be no-op
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToFront(e3) // move from back
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToFront(e3) // should be no-op
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToBack(e3) // move from front
checkListPointers(t, l, []*Element{e1, e4, e3})
l.MoveToBack(e3) // should be no-op
checkListPointers(t, l, []*Element{e1, e4, e3})
l.MoveToBack(e3) // move from front
checkListPointers(t, l, []*Element{e1, e4, e3})
l.MoveToBack(e3) // should be no-op
checkListPointers(t, l, []*Element{e1, e4, e3})
e2 = l.InsertBefore(2, e1) // insert before front
checkListPointers(t, l, []*Element{e2, e1, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e4) // insert before middle
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e3) // insert before back
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e1) // insert before front
checkListPointers(t, l, []*Element{e2, e1, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e4) // insert before middle
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e3) // insert before back
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e1) // insert after front
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e4) // insert after middle
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e3) // insert after back
checkListPointers(t, l, []*Element{e1, e4, e3, e2})
l.Remove(e2)
e2 = l.InsertAfter(2, e1) // insert after front
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e4) // insert after middle
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e3) // insert after back
checkListPointers(t, l, []*Element{e1, e4, e3, e2})
l.Remove(e2)
// Check standard iteration.
sum := 0
for e := l.Front(); e != nil; e = e.Next() {
if i, ok := e.Value.(int); ok {
sum += i
}
}
if sum != 4 {
t.Errorf("sum over l = %d, want 4", sum)
}
// Check standard iteration.
sum := 0
for e := l.Front(); e != nil; e = e.Next() {
if i, ok := e.Value.(int); ok {
sum += i
}
}
if sum != 4 {
t.Errorf("sum over l = %d, want 4", sum)
}
// Clear all elements by iterating
var next *Element
for e := l.Front(); e != nil; e = next {
next = e.Next()
l.Remove(e)
}
checkListPointers(t, l, []*Element{})
// Clear all elements by iterating
var next *Element
for e := l.Front(); e != nil; e = next {
next = e.Next()
l.Remove(e)
}
checkListPointers(t, l, []*Element{})
}
func checkList(t *testing.T, l *List, es []interface{}) {
if !checkListLen(t, l, len(es)) {
return
}
if !checkListLen(t, l, len(es)) {
return
}
i := 0
for e := l.Front(); e != nil; e = e.Next() {
le := e.Value.(int)
if le != es[i] {
t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
}
i++
}
i := 0
for e := l.Front(); e != nil; e = e.Next() {
switch e.Value.(type) {
case int:
if le := e.Value.(int); le != es[i] {
t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
}
// default string
default:
if le := e.Value.(string); le != es[i] {
t.Errorf("elt[%v].Value() = %v, want %v", i, le, es[i])
}
}
i++
}
//for e := l.Front(); e != nil; e = e.Next() {
// le := e.Value.(int)
// if le != es[i] {
// t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
// }
// i++
//}
}
func TestExtending(t *testing.T) {
l1 := New()
l2 := New()
l1 := New()
l2 := New()
l1.PushBack(1)
l1.PushBack(2)
l1.PushBack(3)
l1.PushBack(1)
l1.PushBack(2)
l1.PushBack(3)
l2.PushBack(4)
l2.PushBack(5)
l2.PushBack(4)
l2.PushBack(5)
l3 := New()
l3.PushBackList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushBackList(l2)
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
l3 := New()
l3.PushBackList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushBackList(l2)
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
l3 = New()
l3.PushFrontList(l2)
checkList(t, l3, []interface{}{4, 5})
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
l3 = New()
l3.PushFrontList(l2)
checkList(t, l3, []interface{}{4, 5})
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
checkList(t, l1, []interface{}{1, 2, 3})
checkList(t, l2, []interface{}{4, 5})
checkList(t, l1, []interface{}{1, 2, 3})
checkList(t, l2, []interface{}{4, 5})
l3 = New()
l3.PushBackList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushBackList(l3)
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
l3 = New()
l3.PushBackList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushBackList(l3)
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
l3 = New()
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushFrontList(l3)
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
l3 = New()
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushFrontList(l3)
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
l3 = New()
l1.PushBackList(l3)
checkList(t, l1, []interface{}{1, 2, 3})
l1.PushFrontList(l3)
checkList(t, l1, []interface{}{1, 2, 3})
l3 = New()
l1.PushBackList(l3)
checkList(t, l1, []interface{}{1, 2, 3})
l1.PushFrontList(l3)
checkList(t, l1, []interface{}{1, 2, 3})
}
func TestRemove(t *testing.T) {
l := New()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
checkListPointers(t, l, []*Element{e1, e2})
//e := l.Front()
//l.Remove(e)
//checkListPointers(t, l, []*Element{e2})
//l.Remove(e)
//checkListPointers(t, l, []*Element{e2})
l := New()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
checkListPointers(t, l, []*Element{e1, e2})
//e := l.Front()
//l.Remove(e)
//checkListPointers(t, l, []*Element{e2})
//l.Remove(e)
//checkListPointers(t, l, []*Element{e2})
}
func TestIssue4103(t *testing.T) {
l1 := New()
l1.PushBack(1)
l1.PushBack(2)
l1 := New()
l1.PushBack(1)
l1.PushBack(2)
l2 := New()
l2.PushBack(3)
l2.PushBack(4)
l2 := New()
l2.PushBack(3)
l2.PushBack(4)
e := l1.Front()
l2.Remove(e) // l2 should not change because e is not an element of l2
if n := l2.Len(); n != 2 {
t.Errorf("l2.Len() = %d, want 2", n)
}
e := l1.Front()
l2.Remove(e) // l2 should not change because e is not an element of l2
if n := l2.Len(); n != 2 {
t.Errorf("l2.Len() = %d, want 2", n)
}
l1.InsertBefore(8, e)
if n := l1.Len(); n != 3 {
t.Errorf("l1.Len() = %d, want 3", n)
}
l1.InsertBefore(8, e)
if n := l1.Len(); n != 3 {
t.Errorf("l1.Len() = %d, want 3", n)
}
}
func TestIssue6349(t *testing.T) {
l := New()
l.PushBack(1)
l.PushBack(2)
l := New()
l.PushBack(1)
l.PushBack(2)
e := l.Front()
l.Remove(e)
if e.Value != 1 {
t.Errorf("e.value = %d, want 1", e.Value)
}
//if e.Next() != nil {
// t.Errorf("e.Next() != nil")
//}
//if e.Prev() != nil {
// t.Errorf("e.Prev() != nil")
//}
e := l.Front()
l.Remove(e)
if e.Value != 1 {
t.Errorf("e.value = %d, want 1", e.Value)
}
//if e.Next() != nil {
// t.Errorf("e.Next() != nil")
//}
//if e.Prev() != nil {
// t.Errorf("e.Prev() != nil")
//}
}
func TestMove(t *testing.T) {
l := New()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
e3 := l.PushBack(3)
e4 := l.PushBack(4)
l := New()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
e3 := l.PushBack(3)
e4 := l.PushBack(4)
l.MoveAfter(e3, e3)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveBefore(e2, e2)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveAfter(e3, e3)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveBefore(e2, e2)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveAfter(e3, e2)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveBefore(e2, e3)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveAfter(e3, e2)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveBefore(e2, e3)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveBefore(e2, e4)
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
e2, e3 = e3, e2
l.MoveBefore(e2, e4)
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
e2, e3 = e3, e2
l.MoveBefore(e4, e1)
checkListPointers(t, l, []*Element{e4, e1, e2, e3})
e1, e2, e3, e4 = e4, e1, e2, e3
l.MoveBefore(e4, e1)
checkListPointers(t, l, []*Element{e4, e1, e2, e3})
e1, e2, e3, e4 = e4, e1, e2, e3
l.MoveAfter(e4, e1)
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
e2, e3, e4 = e4, e2, e3
l.MoveAfter(e4, e1)
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
e2, e3, e4 = e4, e2, e3
l.MoveAfter(e2, e3)
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
e2, e3 = e3, e2
l.MoveAfter(e2, e3)
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
e2, e3 = e3, e2
}
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
func TestZeroList(t *testing.T) {
var l1 = New()
l1.PushFront(1)
checkList(t, l1, []interface{}{1})
var l1 = New()
l1.PushFront(1)
checkList(t, l1, []interface{}{1})
var l2 = New()
l2.PushBack(1)
checkList(t, l2, []interface{}{1})
var l2 = New()
l2.PushBack(1)
checkList(t, l2, []interface{}{1})
var l3 = New()
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1})
var l3 = New()
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1})
var l4 = New()
l4.PushBackList(l2)
checkList(t, l4, []interface{}{1})
var l4 = New()
l4.PushBackList(l2)
checkList(t, l4, []interface{}{1})
}
// Test that a list l is not modified when calling InsertBefore with a mark that is not an element of l.
func TestInsertBeforeUnknownMark(t *testing.T) {
l := New()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertBefore(1, new(Element))
checkList(t, l, []interface{}{1, 2, 3})
l := New()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertBefore(1, new(Element))
checkList(t, l, []interface{}{1, 2, 3})
}
// Test that a list l is not modified when calling InsertAfter with a mark that is not an element of l.
func TestInsertAfterUnknownMark(t *testing.T) {
l := New()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertAfter(1, new(Element))
checkList(t, l, []interface{}{1, 2, 3})
l := New()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertAfter(1, new(Element))
checkList(t, l, []interface{}{1, 2, 3})
}
// Test that a list l is not modified when calling MoveAfter or MoveBefore with a mark that is not an element of l.
func TestMoveUnknownMark(t *testing.T) {
l1 := New()
e1 := l1.PushBack(1)
l1 := New()
e1 := l1.PushBack(1)
l2 := New()
e2 := l2.PushBack(2)
l2 := New()
e2 := l2.PushBack(2)
l1.MoveAfter(e1, e2)
checkList(t, l1, []interface{}{1})
checkList(t, l2, []interface{}{2})
l1.MoveAfter(e1, e2)
checkList(t, l1, []interface{}{1})
checkList(t, l2, []interface{}{2})
l1.MoveBefore(e1, e2)
checkList(t, l1, []interface{}{1})
checkList(t, l2, []interface{}{2})
l1.MoveBefore(e1, e2)
checkList(t, l1, []interface{}{1})
checkList(t, l2, []interface{}{2})
}
func TestList_RemoveAll(t *testing.T) {
l := New()
l.PushBack(1)
l.RemoveAll()
checkList(t, l, []interface{}{})
l.PushBack(2)
checkList(t, l, []interface{}{2})
}
l := New()
l.PushBack(1)
l.RemoveAll()
checkList(t, l, []interface{}{})
l.PushBack(2)
checkList(t, l, []interface{}{2})
}
func TestList_PushFronts(t *testing.T) {
l := New()
a1 := []interface{}{1, 2}
l.PushFronts(a1)
checkList(t, l, []interface{}{2, 1})
a1 = []interface{}{3, 4, 5}
l.PushFronts(a1)
checkList(t, l, []interface{}{5, 4, 3, 2, 1})
}
func TestList_PushBacks(t *testing.T) {
l := New()
a1 := []interface{}{1, 2}
l.PushBacks(a1)
checkList(t, l, []interface{}{1, 2})
a1 = []interface{}{3, 4, 5}
l.PushBacks(a1)
checkList(t, l, []interface{}{1, 2, 3, 4, 5})
}
func TestList_PopBacks(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
a2 := []interface{}{"a", "c", "b", "e"}
l.PushFronts(a1)
i1 := l.PopBacks(2)
gtest.Assert(i1, []interface{}{"1", "2"})
l.PushBacks(a2) //4.3,a,c,b,e
i1 = l.PopBacks(3)
gtest.Assert(i1, []interface{}{"e", "b", "c"})
}
func TestList_PopFronts(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.PopFronts(2)
gtest.Assert(i1, []interface{}{"4", "3"})
gtest.Assert(l.Len(), 2)
}
func TestList_PopBackAll(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.PopBackAll()
gtest.Assert(i1, []interface{}{1, 2, 3, 4})
gtest.Assert(l.Len(), 0)
}
func TestList_PopFrontAll(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.PopFrontAll()
gtest.Assert(i1, []interface{}{4, 3, 2, 1})
gtest.Assert(l.Len(), 0)
}
func TestList_FrontAll(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.FrontAll()
gtest.Assert(i1, []interface{}{4, 3, 2, 1})
gtest.Assert(l.Len(), 4)
}
func TestList_BackAll(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.BackAll()
gtest.Assert(i1, []interface{}{1, 2, 3, 4})
gtest.Assert(l.Len(), 4)
}
func TestList_FrontValue(t *testing.T) {
l := New()
l2 := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.FrontValue()
gtest.Assert(gconv.Int(i1), 4)
gtest.Assert(l.Len(), 4)
i1 = l2.FrontValue()
gtest.Assert(i1, nil)
}
func TestList_BackValue(t *testing.T) {
l := New()
l2 := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.BackValue()
gtest.Assert(gconv.Int(i1), 1)
gtest.Assert(l.Len(), 4)
i1 = l2.FrontValue()
gtest.Assert(i1, nil)
}
func TestList_Back(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
e1 := l.Back()
gtest.Assert(e1.Value, 1)
gtest.Assert(l.Len(), 4)
}
func TestList_Size(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
gtest.Assert(l.Size(), 4)
l.PopFront()
gtest.Assert(l.Size(), 3)
}
func TestList_Removes(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
e1 := l.Back()
l.Removes([]*Element{e1})
gtest.Assert(l.Len(), 3)
e2 := l.Back()
l.Removes([]*Element{e2})
gtest.Assert(l.Len(), 2)
checkList(t, l, []interface{}{4, 3})
}
func TestList_Clear(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
l.Clear()
gtest.Assert(l.Len(), 0)
}
func TestList_IteratorAsc(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 5, 6, 3, 4}
l.PushFronts(a1)
e1 := l.Back()
fun1 := func(e *Element) bool {
if gconv.Int(e1.Value) > 2 {
return true
}
return false
}
checkList(t, l, []interface{}{4, 3, 6, 5, 2, 1})
l.IteratorAsc(fun1)
checkList(t, l, []interface{}{4, 3, 6, 5, 2, 1})
}
func TestList_IteratorDesc(t *testing.T) {
l := New()
a1 := []interface{}{1, 2, 3, 4}
l.PushFronts(a1)
e1 := l.Back()
fun1 := func(e *Element) bool {
if gconv.Int(e1.Value) > 6 {
return true
}
return false
}
l.IteratorDesc(fun1)
gtest.Assert(l.Len(), 4)
checkList(t, l, []interface{}{4, 3, 2, 1})
}
func TestList_Iterator(t *testing.T) {
l := New()
a1 := []interface{}{"a", "b", "c", "d", "e"}
l.PushFronts(a1)
e1 := l.Back()
fun1 := func(e *Element) bool {
if gconv.String(e1.Value) > "c" {
return true
}
return false
}
checkList(t, l, []interface{}{"e", "d", "c", "b", "a"})
l.Iterator(fun1)
checkList(t, l, []interface{}{"e", "d", "c", "b", "a"})
}

View File

@ -1,21 +1,44 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// 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 kinds of concurrent-safe(alternative) maps.
//
// 并发安全的哈希MAP.
// Package gmap provides concurrent-safe/unsafe map containers.
package gmap
// 默认的Map对象其实就是InterfaceInterfaceMap的别名。
// 注意
// 1、这个Map是所有并发安全Map中效率最低的如果对效率要求比较高的场合请合理选择对应数据类型的Map
// 2、这个Map的优点是使用简便由于键值都是interface{}类型,因此对键值的数据类型要求不高;
// 3、底层实现比较类似于sync.Map
type Map = InterfaceInterfaceMap
// Map based on hash table, alias of AnyAnyMap.
type Map = AnyAnyMap
type HashMap = AnyAnyMap
func New(safe...bool) *Map {
return NewInterfaceInterfaceMap(safe...)
}
// New returns an empty hash map.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func New(unsafe ...bool) *Map {
return NewAnyAnyMap(unsafe...)
}
// NewFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewFrom(data map[interface{}]interface{}, unsafe ...bool) *Map {
return NewAnyAnyMapFrom(data, unsafe...)
}
// NewHashMap returns an empty hash map.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewHashMap(unsafe ...bool) *Map {
return NewAnyAnyMap(unsafe...)
}
// NewHashMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewHashMapFrom(data map[interface{}]interface{}, unsafe ...bool) *Map {
return NewAnyAnyMapFrom(data, unsafe...)
}

View File

@ -1,139 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*" -benchmem
package gmap
import (
"testing"
"strconv"
)
var ibm = NewIntBoolMap()
var iim = NewIntIntMap()
var iifm = NewIntInterfaceMap()
var ism = NewIntStringMap()
var ififm = NewInterfaceInterfaceMap()
var sbm = NewStringBoolMap()
var sim = NewStringIntMap()
var sifm = NewStringInterfaceMap()
var ssm = NewStringStringMap()
// 写入性能测试
func Benchmark_IntBoolMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ibm.Set(i, true)
}
}
func Benchmark_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iim.Set(i, i)
}
}
func Benchmark_IntInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iifm.Set(i, i)
}
}
func Benchmark_IntStringMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ism.Set(i, strconv.Itoa(i))
}
}
func Benchmark_InterfaceInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ififm.Set(i, i)
}
}
func Benchmark_StringBoolMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sbm.Set(strconv.Itoa(i), true)
}
}
func Benchmark_StringIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sim.Set(strconv.Itoa(i), i)
}
}
func Benchmark_StringInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sifm.Set(strconv.Itoa(i), i)
}
}
func Benchmark_StringStringMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ssm.Set(strconv.Itoa(i), strconv.Itoa(i))
}
}
// 读取性能测试
func Benchmark_IntBoolMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ibm.Get(i)
}
}
func Benchmark_IntIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iim.Get(i)
}
}
func Benchmark_IntInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iifm.Get(i)
}
}
func Benchmark_IntStringMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ism.Get(i)
}
}
func Benchmark_InterfaceInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ififm.Get(i)
}
}
func Benchmark_StringBoolMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sbm.Get(strconv.Itoa(i))
}
}
func Benchmark_StringIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sim.Get(strconv.Itoa(i))
}
}
func Benchmark_StringInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sifm.Get(strconv.Itoa(i))
}
}
func Benchmark_StringStringMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ssm.Get(strconv.Itoa(i))
}
}

View File

@ -1,56 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"testing"
"gitee.com/johng/gf/g/container/gmap"
"sync"
)
var m1 = gmap.NewIntIntMap()
var m2 = sync.Map{}
func BenchmarkGmapSet(b *testing.B) {
for i := 0; i < b.N; i++ {
m1.Set(i, i)
}
}
func BenchmarkSyncmapSet(b *testing.B) {
for i := 0; i < b.N; i++ {
m2.Store(i, i)
}
}
func BenchmarkGmapGet(b *testing.B) {
for i := 0; i < b.N; i++ {
m1.Get(i)
}
}
func BenchmarkSyncmapGet(b *testing.B) {
for i := 0; i < b.N; i++ {
m2.Load(i)
}
}
func BenchmarkGmapRemove(b *testing.B) {
for i := 0; i < b.N; i++ {
m1.Remove(i)
}
}
func BenchmarkSyncmapRmove(b *testing.B) {
for i := 0; i < b.N; i++ {
m2.Delete(i)
}
}

View File

@ -1,139 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*" -benchmem
package gmap
import (
"testing"
"strconv"
)
var ibmUnsafe = NewIntBoolMap(false)
var iimUnsafe = NewIntIntMap(false)
var iifmUnsafe = NewIntInterfaceMap(false)
var ismUnsafe = NewIntStringMap(false)
var ififmUnsafe = NewInterfaceInterfaceMap(false)
var sbmUnsafe = NewStringBoolMap(false)
var simUnsafe = NewStringIntMap(false)
var sifmUnsafe = NewStringInterfaceMap(false)
var ssmUnsafe = NewStringStringMap(false)
// 写入性能测试
func Benchmark_Unsafe_IntBoolMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ibmUnsafe.Set(i, true)
}
}
func Benchmark_Unsafe_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iimUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_IntInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iifmUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_IntStringMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ismUnsafe.Set(i, strconv.Itoa(i))
}
}
func Benchmark_Unsafe_InterfaceInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ififmUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_StringBoolMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sbmUnsafe.Set(strconv.Itoa(i), true)
}
}
func Benchmark_Unsafe_StringIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
simUnsafe.Set(strconv.Itoa(i), i)
}
}
func Benchmark_Unsafe_StringInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sifmUnsafe.Set(strconv.Itoa(i), i)
}
}
func Benchmark_Unsafe_StringStringMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ssmUnsafe.Set(strconv.Itoa(i), strconv.Itoa(i))
}
}
// 读取性能测试
func Benchmark_Unsafe_IntBoolMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ibmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iimUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iifmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntStringMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ismUnsafe.Get(i)
}
}
func Benchmark_Unsafe_InterfaceInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ififmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_StringBoolMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sbmUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
simUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sifmUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringStringMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ssmUnsafe.Get(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,330 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
)
type AnyAnyMap struct {
mu *rwmutex.RWMutex
data map[interface{}]interface{}
}
// NewAnyAnyMap returns an empty hash map.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewAnyAnyMap(unsafe ...bool) *AnyAnyMap {
return &AnyAnyMap{
mu: rwmutex.New(unsafe...),
data: make(map[interface{}]interface{}),
}
}
// NewAnyAnyMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewAnyAnyMapFrom(data map[interface{}]interface{}, unsafe ...bool) *AnyAnyMap {
return &AnyAnyMap{
mu: rwmutex.New(unsafe...),
data: data,
}
}
// Iterator iterates the hash map 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()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *AnyAnyMap) Clone(unsafe ...bool) *AnyAnyMap {
return NewFrom(m.Map(), unsafe...)
}
// Map returns a copy of the data of the hash map.
func (m *AnyAnyMap) Map() map[interface{}]interface{} {
m.mu.RLock()
data := make(map[interface{}]interface{}, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *AnyAnyMap) Set(key interface{}, val interface{}) {
m.mu.Lock()
m.data[key] = val
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
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// 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]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *AnyAnyMap) Get(key interface{}) interface{} {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *AnyAnyMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok := m.data[key]; ok {
return v
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
m.data[key] = value
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *AnyAnyMap) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *AnyAnyMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *AnyAnyMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *AnyAnyMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *AnyAnyMap) SetIfNotExist(key interface{}, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *AnyAnyMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *AnyAnyMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *AnyAnyMap) Remove(key interface{}) interface{} {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// 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)
}
m.mu.Unlock()
}
// 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
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
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 {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *AnyAnyMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *AnyAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *AnyAnyMap) Clear() {
m.mu.Lock()
m.data = make(map[interface{}]interface{})
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *AnyAnyMap) LockFunc(f func(m map[interface{}]interface{})) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *AnyAnyMap) RLockFunc(f func(m map[interface{}]interface{})) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *AnyAnyMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[interface{}]interface{}, len(m.data))
for k, v := range m.data {
n[v] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,332 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
//
package gmap
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type IntAnyMap struct {
mu *rwmutex.RWMutex
data map[int]interface{}
}
// NewIntAnyMap returns an empty IntAnyMap object.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewIntAnyMap(unsafe ...bool) *IntAnyMap {
return &IntAnyMap{
mu: rwmutex.New(unsafe...),
data: make(map[int]interface{}),
}
}
// NewIntAnyMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewIntAnyMapFrom(data map[int]interface{}, unsafe ...bool) *IntAnyMap {
return &IntAnyMap{
mu: rwmutex.New(unsafe...),
data: data,
}
}
// Iterator iterates the hash map 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()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *IntAnyMap) Clone() *IntAnyMap {
return NewIntAnyMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *IntAnyMap) Map() map[int]interface{} {
m.mu.RLock()
data := make(map[int]interface{}, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *IntAnyMap) Set(key int, val interface{}) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// 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
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// 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]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntAnyMap) Get(key int) interface{} {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *IntAnyMap) doSetWithLockCheck(key int, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok := m.data[key]; ok {
return v
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
if value != nil {
m.data[key] = value
}
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *IntAnyMap) GetOrSet(key int, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
func (m *IntAnyMap) GetOrSetFunc(key int, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *IntAnyMap) GetOrSetFuncLock(key int, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *IntAnyMap) GetVar(key int) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSet(key int, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSetFunc(key int, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSetFuncLock(key int, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntAnyMap) SetIfNotExist(key int, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntAnyMap) SetIfNotExistFunc(key int, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *IntAnyMap) SetIfNotExistFuncLock(key int, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// 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)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntAnyMap) Remove(key int) interface{} {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// 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
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 *IntAnyMap) Values() []interface{} {
m.mu.RLock()
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 *IntAnyMap) Contains(key int) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *IntAnyMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntAnyMap) Clear() {
m.mu.Lock()
m.data = make(map[int]interface{})
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *IntAnyMap) LockFunc(f func(m map[int]interface{})) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *IntAnyMap) RLockFunc(f func(m map[int]interface{})) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *IntAnyMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[int]interface{}, len(m.data))
for k, v := range m.data {
n[gconv.Int(v)] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *IntAnyMap) Merge(other *IntAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,308 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/internal/rwmutex"
)
type IntIntMap struct {
mu *rwmutex.RWMutex
data map[int]int
}
// NewIntIntMap returns an empty IntIntMap object.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewIntIntMap(unsafe ...bool) *IntIntMap {
return &IntIntMap{
mu: rwmutex.New(unsafe...),
data: make(map[int]int),
}
}
// NewIntIntMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewIntIntMapFrom(data map[int]int, unsafe ...bool) *IntIntMap {
return &IntIntMap{
mu: rwmutex.New(unsafe...),
data: data,
}
}
// Iterator iterates the hash map 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()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *IntIntMap) Clone() *IntIntMap {
return NewIntIntMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *IntIntMap) Map() map[int]int {
m.mu.RLock()
data := make(map[int]int, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *IntIntMap) Set(key int, val int) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// 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
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// 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]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntIntMap) Get(key int) int {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// It returns value with given <key>.
func (m *IntIntMap) doSetWithLockCheck(key int, value int) int {
m.mu.Lock()
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *IntIntMap) GetOrSet(key int, value int) int {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
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 v, ok = m.data[key]; ok {
return v
}
v = f()
m.data[key] = v
return v
} else {
return v
}
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntIntMap) SetIfNotExist(key int, value int) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
return true
}
return false
}
// 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)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntIntMap) Remove(key int) int {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// 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
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 *IntIntMap) Values() []int {
m.mu.RLock()
values := make([]int, 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 *IntIntMap) Contains(key int) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *IntIntMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntIntMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntIntMap) Clear() {
m.mu.Lock()
m.data = make(map[int]int)
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *IntIntMap) LockFunc(f func(m map[int]int)) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *IntIntMap) RLockFunc(f func(m map[int]int)) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *IntIntMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[int]int, len(m.data))
for k, v := range m.data {
n[v] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *IntIntMap) Merge(other *IntIntMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,309 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type IntStrMap struct {
mu *rwmutex.RWMutex
data map[int]string
}
// NewIntStrMap returns an empty IntStrMap object.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewIntStrMap(unsafe ...bool) *IntStrMap {
return &IntStrMap{
mu: rwmutex.New(unsafe...),
data: make(map[int]string),
}
}
// NewIntStrMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewIntStrMapFrom(data map[int]string, unsafe ...bool) *IntStrMap {
return &IntStrMap{
mu: rwmutex.New(unsafe...),
data: data,
}
}
// Iterator iterates the hash map 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()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *IntStrMap) Clone() *IntStrMap {
return NewIntStrMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *IntStrMap) Map() map[int]string {
m.mu.RLock()
data := make(map[int]string, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *IntStrMap) Set(key int, val string) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// 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
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// 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]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntStrMap) Get(key int) string {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// It returns value with given <key>.
func (m *IntStrMap) doSetWithLockCheck(key int, value string) string {
m.mu.Lock()
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *IntStrMap) GetOrSet(key int, value string) string {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
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 v, ok = m.data[key]; ok {
return v
}
v = f()
m.data[key] = v
return v
} else {
return v
}
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntStrMap) SetIfNotExist(key int, value string) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
return true
}
return false
}
// 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)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntStrMap) Remove(key int) string {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// 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
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 *IntStrMap) Values() []string {
m.mu.RLock()
values := make([]string, 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 *IntStrMap) Contains(key int) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *IntStrMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntStrMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntStrMap) Clear() {
m.mu.Lock()
m.data = make(map[int]string)
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *IntStrMap) LockFunc(f func(m map[int]string)) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *IntStrMap) RLockFunc(f func(m map[int]string)) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *IntStrMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[int]string, len(m.data))
for k, v := range m.data {
n[gconv.Int(v)] = gconv.String(k)
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *IntStrMap) Merge(other *IntStrMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,334 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
//
package gmap
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type StrAnyMap struct {
mu *rwmutex.RWMutex
data map[string]interface{}
}
// NewStrAnyMap returns an empty StrAnyMap object.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewStrAnyMap(unsafe ...bool) *StrAnyMap {
return &StrAnyMap{
mu: rwmutex.New(unsafe...),
data: make(map[string]interface{}),
}
}
// NewStrAnyMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewStrAnyMapFrom(data map[string]interface{}, unsafe ...bool) *StrAnyMap {
return &StrAnyMap{
mu: rwmutex.New(unsafe...),
data: data,
}
}
// Iterator iterates the hash map 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()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *StrAnyMap) Clone() *StrAnyMap {
return NewStrAnyMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *StrAnyMap) Map() map[string]interface{} {
m.mu.RLock()
data := make(map[string]interface{}, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *StrAnyMap) Set(key string, val interface{}) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// 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
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// 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]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrAnyMap) Get(key string) interface{} {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *StrAnyMap) doSetWithLockCheck(key string, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok := m.data[key]; ok {
return v
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
if value != nil {
m.data[key] = value
}
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *StrAnyMap) GetOrSet(key string, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *StrAnyMap) GetOrSetFunc(key string, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *StrAnyMap) GetVar(key string) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSet(key string, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSetFunc(key string, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSetFuncLock(key string, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrAnyMap) SetIfNotExist(key string, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrAnyMap) SetIfNotExistFunc(key string, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *StrAnyMap) SetIfNotExistFuncLock(key string, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// 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)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrAnyMap) Remove(key string) interface{} {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// 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
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 *StrAnyMap) Values() []interface{} {
m.mu.RLock()
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 *StrAnyMap) Contains(key string) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *StrAnyMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrAnyMap) Clear() {
m.mu.Lock()
m.data = make(map[string]interface{})
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *StrAnyMap) LockFunc(f func(m map[string]interface{})) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *StrAnyMap) RLockFunc(f func(m map[string]interface{})) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *StrAnyMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[string]interface{}, len(m.data))
for k, v := range m.data {
n[gconv.String(v)] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *StrAnyMap) Merge(other *StrAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,312 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
//
package gmap
import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type StrIntMap struct {
mu *rwmutex.RWMutex
data map[string]int
}
// NewStrIntMap returns an empty StrIntMap object.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewStrIntMap(unsafe ...bool) *StrIntMap {
return &StrIntMap{
mu: rwmutex.New(unsafe...),
data: make(map[string]int),
}
}
// NewStrIntMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewStrIntMapFrom(data map[string]int, unsafe ...bool) *StrIntMap {
return &StrIntMap{
mu: rwmutex.New(unsafe...),
data: data,
}
}
// Iterator iterates the hash map 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()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *StrIntMap) Clone() *StrIntMap {
return NewStrIntMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *StrIntMap) Map() map[string]int {
m.mu.RLock()
data := make(map[string]int, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *StrIntMap) Set(key string, val int) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// 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
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// 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]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrIntMap) Get(key string) int {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// It returns value with given <key>.
func (m *StrIntMap) doSetWithLockCheck(key string, value int) int {
m.mu.Lock()
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *StrIntMap) GetOrSet(key string, value int) int {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *StrIntMap) GetOrSetFunc(key string, f func() int) int {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
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 v, ok = m.data[key]; ok {
return v
}
v = f()
m.data[key] = v
return v
} else {
return v
}
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrIntMap) SetIfNotExist(key string, value int) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
return true
}
return false
}
// 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)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrIntMap) Remove(key string) int {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// 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
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 *StrIntMap) Values() []int {
m.mu.RLock()
values := make([]int, 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 *StrIntMap) Contains(key string) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *StrIntMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrIntMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrIntMap) Clear() {
m.mu.Lock()
m.data = make(map[string]int)
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *StrIntMap) LockFunc(f func(m map[string]int)) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *StrIntMap) RLockFunc(f func(m map[string]int)) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *StrIntMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[string]int, len(m.data))
for k, v := range m.data {
n[gconv.String(v)] = gconv.Int(k)
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *StrIntMap) Merge(other *StrIntMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,311 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
//
package gmap
import (
"github.com/gogf/gf/g/internal/rwmutex"
)
type StrStrMap struct {
mu *rwmutex.RWMutex
data map[string]string
}
// NewStrStrMap returns an empty StrStrMap object.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewStrStrMap(unsafe ...bool) *StrStrMap {
return &StrStrMap{
data: make(map[string]string),
mu: rwmutex.New(unsafe...),
}
}
// NewStrStrMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewStrStrMapFrom(data map[string]string, unsafe ...bool) *StrStrMap {
return &StrStrMap{
mu: rwmutex.New(unsafe...),
data: data,
}
}
// Iterator iterates the hash map 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()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *StrStrMap) Clone() *StrStrMap {
return NewStrStrMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *StrStrMap) Map() map[string]string {
m.mu.RLock()
data := make(map[string]string, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *StrStrMap) Set(key string, val string) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// 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
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// 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]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrStrMap) Get(key string) string {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// It returns value with given <key>.
func (m *StrStrMap) doSetWithLockCheck(key string, value string) string {
m.mu.Lock()
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *StrStrMap) GetOrSet(key string, value string) string {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *StrStrMap) GetOrSetFunc(key string, f func() string) string {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
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 v, ok = m.data[key]; ok {
return v
}
v = f()
m.data[key] = v
return v
} else {
return v
}
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrStrMap) SetIfNotExist(key string, value string) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
return true
}
return false
}
// 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)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrStrMap) Remove(key string) string {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// 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
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 *StrStrMap) Values() []string {
m.mu.RLock()
values := make([]string, 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 *StrStrMap) Contains(key string) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *StrStrMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrStrMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrStrMap) Clear() {
m.mu.Lock()
m.data = make(map[string]string)
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *StrStrMap) LockFunc(f func(m map[string]string)) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *StrStrMap) RLockFunc(f func(m map[string]string)) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *StrStrMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[string]string, len(m.data))
for k, v := range m.data {
n[v] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *StrStrMap) Merge(other *StrStrMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -1,223 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
//
package gmap
import (
"gitee.com/johng/gf/g/internal/rwmutex"
)
type IntBoolMap struct {
m map[int]bool
mu *rwmutex.RWMutex
}
func NewIntBoolMap(unsafe...bool) *IntBoolMap {
return &IntBoolMap{
m : make(map[int]bool),
mu : rwmutex.New(unsafe...),
}
}
// 哈希表克隆
func (this *IntBoolMap) Clone() map[int]bool {
m := make(map[int]bool)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntBoolMap) Iterator(f func (k int, v bool) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 设置键值对
func (this *IntBoolMap) Set(key int, val bool) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *IntBoolMap) BatchSet(m map[int]bool) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *IntBoolMap) Get(key int) bool {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *IntBoolMap) doSetWithLockCheck(key int, value bool) bool {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntBoolMap) GetOrSet(key int, value bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *IntBoolMap) GetOrSetFunc(key int, f func() bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *IntBoolMap) GetOrSetFuncLock(key int, f func() bool) bool {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntBoolMap) SetIfNotExist(key int, value bool) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntBoolMap) BatchRemove(keys []int) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntBoolMap) Remove(key int) bool {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *IntBoolMap) Keys() []int {
this.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
//func (this *IntBoolMap) Values() []bool {
// this.mu.RLock()
// vals := make([]bool, 0)
// for _, val := range this.m {
// vals = append(vals, val)
// }
// this.mu.RUnlock()
// return vals
//}
// 是否存在某个键
func (this *IntBoolMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntBoolMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntBoolMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntBoolMap) Clear() {
this.mu.Lock()
this.m = make(map[int]bool)
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntBoolMap) LockFunc(f func(m map[int]bool)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntBoolMap) RLockFunc(f func(m map[int]bool)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,223 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package gmap
import (
"gitee.com/johng/gf/g/internal/rwmutex"
)
type IntIntMap struct {
mu *rwmutex.RWMutex
m map[int]int
}
func NewIntIntMap(unsafe...bool) *IntIntMap {
return &IntIntMap{
m : make(map[int]int),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntIntMap) Iterator(f func (k int, v int) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntIntMap) Clone() map[int]int {
m := make(map[int]int)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntIntMap) Set(key int, val int) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *IntIntMap) BatchSet(m map[int]int) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *IntIntMap) Get(key int) (int) {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *IntIntMap) doSetWithLockCheck(key int, value int) int {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntIntMap) GetOrSet(key int, value int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *IntIntMap) GetOrSetFunc(key int, f func() int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntIntMap) SetIfNotExist(key int, value int) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntIntMap) BatchRemove(keys []int) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntIntMap) Remove(key int) int {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *IntIntMap) Keys() []int {
this.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntIntMap) Values() []int {
this.mu.RLock()
vals := make([]int, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntIntMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntIntMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntIntMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntIntMap) Clear() {
this.mu.Lock()
this.m = make(map[int]int)
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntIntMap) LockFunc(f func(m map[int]int)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntIntMap) RLockFunc(f func(m map[int]int)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,206 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
//
package gmap
import "gitee.com/johng/gf/g/internal/rwmutex"
type IntInterfaceMap struct {
mu *rwmutex.RWMutex
m map[int]interface{}
}
func NewIntInterfaceMap(unsafe...bool) *IntInterfaceMap {
return &IntInterfaceMap{
m : make(map[int]interface{}),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntInterfaceMap) Iterator(f func (k int, v interface{}) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntInterfaceMap) Clone() map[int]interface{} {
m := make(map[int]interface{})
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntInterfaceMap) Set(key int, val interface{}) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *IntInterfaceMap) BatchSet(m map[int]interface{}) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *IntInterfaceMap) Get(key int) (interface{}) {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *IntInterfaceMap) doSetWithLockCheck(key int, value interface{}) interface{} {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
this.m[key] = value
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntInterfaceMap) GetOrSet(key int, value interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *IntInterfaceMap) GetOrSetFunc(key int, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *IntInterfaceMap) GetOrSetFuncLock(key int, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntInterfaceMap) SetIfNotExist(key int, value interface{}) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntInterfaceMap) BatchRemove(keys []int) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntInterfaceMap) Remove(key int) interface{} {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *IntInterfaceMap) Keys() []int {
this.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntInterfaceMap) Values() []interface{} {
this.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntInterfaceMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntInterfaceMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntInterfaceMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntInterfaceMap) Clear() {
this.mu.Lock()
this.m = make(map[int]interface{})
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntInterfaceMap) LockFunc(f func(m map[int]interface{})) {
this.mu.Lock()
defer this.mu.Unlock()
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntInterfaceMap) RLockFunc(f func(m map[int]interface{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,223 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package gmap
import (
"gitee.com/johng/gf/g/internal/rwmutex"
)
type IntStringMap struct {
mu *rwmutex.RWMutex
m map[int]string
}
func NewIntStringMap(unsafe...bool) *IntStringMap {
return &IntStringMap{
m : make(map[int]string),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntStringMap) Iterator(f func (k int, v string) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntStringMap) Clone() map[int]string {
m := make(map[int]string)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntStringMap) Set(key int, val string) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *IntStringMap) BatchSet(m map[int]string) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *IntStringMap) Get(key int) string {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *IntStringMap) doSetWithLockCheck(key int, value string) string {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntStringMap) GetOrSet(key int, value string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *IntStringMap) GetOrSetFunc(key int, f func() string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *IntStringMap) GetOrSetFuncLock(key int, f func() string) string {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntStringMap) SetIfNotExist(key int, value string) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntStringMap) BatchRemove(keys []int) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntStringMap) Remove(key int) string {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *IntStringMap) Keys() []int {
this.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntStringMap) Values() []string {
this.mu.RLock()
vals := make([]string, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntStringMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntStringMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntStringMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntStringMap) Clear() {
this.mu.Lock()
this.m = make(map[int]string)
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntStringMap) LockFunc(f func(m map[int]string)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntStringMap) RLockFunc(f func(m map[int]string)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,208 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
//
package gmap
import (
"gitee.com/johng/gf/g/internal/rwmutex"
)
type InterfaceInterfaceMap struct {
mu *rwmutex.RWMutex
m map[interface{}]interface{}
}
func NewInterfaceInterfaceMap(unsafe...bool) *InterfaceInterfaceMap {
return &InterfaceInterfaceMap{
m : make(map[interface{}]interface{}),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *InterfaceInterfaceMap) Iterator(f func (k interface{}, v interface{}) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *InterfaceInterfaceMap) Clone() map[interface{}]interface{} {
m := make(map[interface{}]interface{})
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *InterfaceInterfaceMap) Set(key interface{}, val interface{}) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *InterfaceInterfaceMap) BatchSet(m map[interface{}]interface{}) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *InterfaceInterfaceMap) Get(key interface{}) interface{} {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *InterfaceInterfaceMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
this.m[key] = value
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *InterfaceInterfaceMap) GetOrSet(key interface{}, value interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *InterfaceInterfaceMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *InterfaceInterfaceMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *InterfaceInterfaceMap) SetIfNotExist(key interface{}, value interface{}) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *InterfaceInterfaceMap) BatchRemove(keys []interface{}) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *InterfaceInterfaceMap) Remove(key interface{}) interface{} {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *InterfaceInterfaceMap) Keys() []interface{} {
this.mu.RLock()
keys := make([]interface{}, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *InterfaceInterfaceMap) Values() []interface{} {
this.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *InterfaceInterfaceMap) Contains(key interface{}) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *InterfaceInterfaceMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *InterfaceInterfaceMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *InterfaceInterfaceMap) Clear() {
this.mu.Lock()
this.m = make(map[interface{}]interface{})
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *InterfaceInterfaceMap) LockFunc(f func(m map[interface{}]interface{})) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *InterfaceInterfaceMap) RLockFunc(f func(m map[interface{}]interface{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -0,0 +1,366 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/container/glist"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
)
type ListMap struct {
mu *rwmutex.RWMutex
data map[interface{}]*glist.Element
list *glist.List
}
type gListMapNode struct {
key interface{}
value interface{}
}
// NewListMap returns an empty link map.
// ListMap is backed by a hash table to store values and doubly-linked list to store ordering.
// The parameter <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewListMap(unsafe ...bool) *ListMap {
return &ListMap{
mu: rwmutex.New(unsafe...),
data: make(map[interface{}]*glist.Element),
list: glist.New(true),
}
}
// NewListMapFrom returns a link map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewListMapFrom(data map[interface{}]interface{}, unsafe ...bool) *ListMap {
m := NewListMap(unsafe...)
m.Sets(data)
return m
}
// Iterator is alias of IteratorAsc.
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>.
// 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)
})
}
// IteratorDesc iterates the map 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)
})
}
// Clone returns a new link map with copy of current map data.
func (m *ListMap) Clone(unsafe ...bool) *ListMap {
return NewListMapFrom(m.Map(), unsafe...)
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *ListMap) Clear() {
m.mu.Lock()
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New(true)
m.mu.Unlock()
}
// Map returns a copy of the 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
})
m.mu.RUnlock()
return data
}
// Set sets key-value to the map.
func (m *ListMap) Set(key interface{}, value interface{}) {
m.mu.Lock()
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
} else {
e.Value = &gListMapNode{key, value}
}
m.mu.Unlock()
}
// Sets batch sets key-values to the map.
func (m *ListMap) Sets(data map[interface{}]interface{}) {
m.mu.Lock()
for key, value := range data {
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
} else {
e.Value = &gListMapNode{key, value}
}
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// 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
}
m.mu.RUnlock()
return
}
// 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
}
m.mu.RUnlock()
return
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *ListMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if e, ok := m.data[key]; ok {
return e.Value.(*gListMapNode).value
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *ListMap) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *ListMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the map.
func (m *ListMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *ListMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *ListMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *ListMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *ListMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *ListMap) SetIfNotExist(key interface{}, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *ListMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the map.
func (m *ListMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// 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)
}
m.mu.Unlock()
return
}
// 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)
}
}
m.mu.Unlock()
}
// 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
})
m.mu.RUnlock()
return keys
}
// 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
})
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// 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]
m.mu.RUnlock()
return
}
// Size returns the size of the map.
func (m *ListMap) Size() (size int) {
m.mu.RLock()
size = len(m.data)
m.mu.RUnlock()
return
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *ListMap) IsEmpty() bool {
return m.Size() == 0
}
// Flip exchanges key-value of the map to value-key.
func (m *ListMap) Flip() {
data := m.Map()
m.Clear()
for key, value := range data {
m.Set(value, key)
}
}
// Merge merges two link maps.
// The <other> map will be merged into the map <m>.
func (m *ListMap) Merge(other *ListMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
node := (*gListMapNode)(nil)
other.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if e, ok := m.data[node.key]; !ok {
m.data[node.key] = m.list.PushBack(&gListMapNode{node.key, node.value})
} else {
e.Value = &gListMapNode{node.key, node.value}
}
return true
})
}

View File

@ -1,223 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
//
package gmap
import (
"gitee.com/johng/gf/g/internal/rwmutex"
)
type StringBoolMap struct {
mu *rwmutex.RWMutex
m map[string]bool
}
func NewStringBoolMap(unsafe...bool) *StringBoolMap {
return &StringBoolMap{
m : make(map[string]bool),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringBoolMap) Iterator(f func (k string, v bool) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringBoolMap) Clone() map[string]bool {
m := make(map[string]bool)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringBoolMap) Set(key string, val bool) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *StringBoolMap) BatchSet(m map[string]bool) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *StringBoolMap) Get(key string) bool {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *StringBoolMap) doSetWithLockCheck(key string, value bool) bool {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringBoolMap) GetOrSet(key string, value bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *StringBoolMap) GetOrSetFunc(key string, f func() bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *StringBoolMap) GetOrSetFuncLock(key string, f func() bool) bool {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringBoolMap) SetIfNotExist(key string, value bool) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringBoolMap) BatchRemove(keys []string) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringBoolMap) Remove(key string) bool {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *StringBoolMap) Keys() []string {
this.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
//func (this *StringBoolMap) Values() []bool {
// this.mu.RLock()
// vals := make([]bool, 0)
// for _, val := range this.m {
// vals = append(vals, val)
// }
// this.mu.RUnlock()
// return vals
//}
// 是否存在某个键
func (this *StringBoolMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringBoolMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringBoolMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringBoolMap) Clear() {
this.mu.Lock()
this.m = make(map[string]bool)
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *StringBoolMap) LockFunc(f func(m map[string]bool)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *StringBoolMap) RLockFunc(f func(m map[string]bool)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,221 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
//
package gmap
import "gitee.com/johng/gf/g/internal/rwmutex"
type StringIntMap struct {
mu *rwmutex.RWMutex
m map[string]int
}
func NewStringIntMap(unsafe...bool) *StringIntMap {
return &StringIntMap{
m : make(map[string]int),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringIntMap) Iterator(f func (k string, v int) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringIntMap) Clone() map[string]int {
m := make(map[string]int)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringIntMap) Set(key string, val int) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *StringIntMap) BatchSet(m map[string]int) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *StringIntMap) Get(key string) int {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *StringIntMap) doSetWithLockCheck(key string, value int) int {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringIntMap) GetOrSet(key string, value int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *StringIntMap) GetOrSetFunc(key string, f func() int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *StringIntMap) GetOrSetFuncLock(key string, f func() int) int {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringIntMap) SetIfNotExist(key string, value int) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringIntMap) BatchRemove(keys []string) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringIntMap) Remove(key string) int {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *StringIntMap) Keys() []string {
this.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringIntMap) Values() []int {
this.mu.RLock()
vals := make([]int, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringIntMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringIntMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringIntMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringIntMap) Clear() {
this.mu.Lock()
this.m = make(map[string]int)
this.mu.Unlock()
}
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (this *StringIntMap) LockFunc(f func(m map[string]int)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringIntMap) RLockFunc(f func(m map[string]int)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,208 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
//
package gmap
import (
"gitee.com/johng/gf/g/internal/rwmutex"
)
type StringInterfaceMap struct {
mu *rwmutex.RWMutex
m map[string]interface{}
}
func NewStringInterfaceMap(unsafe...bool) *StringInterfaceMap {
return &StringInterfaceMap{
m : make(map[string]interface{}),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringInterfaceMap) Iterator(f func (k string, v interface{}) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringInterfaceMap) Clone() map[string]interface{} {
m := make(map[string]interface{})
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringInterfaceMap) Set(key string, val interface{}) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *StringInterfaceMap) BatchSet(m map[string]interface{}) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *StringInterfaceMap) Get(key string) interface{} {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *StringInterfaceMap) doSetWithLockCheck(key string, value interface{}) interface{} {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
this.m[key] = value
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringInterfaceMap) GetOrSet(key string, value interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *StringInterfaceMap) GetOrSetFunc(key string, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *StringInterfaceMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringInterfaceMap) SetIfNotExist(key string, value interface{}) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringInterfaceMap) BatchRemove(keys []string) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringInterfaceMap) Remove(key string) interface{} {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *StringInterfaceMap) Keys() []string {
this.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringInterfaceMap) Values() []interface{} {
this.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringInterfaceMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringInterfaceMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringInterfaceMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringInterfaceMap) Clear() {
this.mu.Lock()
this.m = make(map[string]interface{})
this.mu.Unlock()
}
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (this *StringInterfaceMap) LockFunc(f func(m map[string]interface{})) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringInterfaceMap) RLockFunc(f func(m map[string]interface{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,221 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
//
package gmap
import "gitee.com/johng/gf/g/internal/rwmutex"
type StringStringMap struct {
mu *rwmutex.RWMutex
m map[string]string
}
func NewStringStringMap(unsafe...bool) *StringStringMap {
return &StringStringMap{
m : make(map[string]string),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringStringMap) Iterator(f func (k string, v string) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringStringMap) Clone() map[string]string {
m := make(map[string]string)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringStringMap) Set(key string, val string) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *StringStringMap) BatchSet(m map[string]string) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *StringStringMap) Get(key string) string {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *StringStringMap) doSetWithLockCheck(key string, value string) string {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringStringMap) GetOrSet(key string, value string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *StringStringMap) GetOrSetFunc(key string, f func() string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *StringStringMap) GetOrSetFuncLock(key string, f func() string) string {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringStringMap) SetIfNotExist(key string, value string) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringStringMap) BatchRemove(keys []string) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringStringMap) Remove(key string) string {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *StringStringMap) Keys() []string {
this.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringStringMap) Values() []string {
this.mu.RLock()
vals := make([]string, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringStringMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringStringMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringStringMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringStringMap) Clear() {
this.mu.Lock()
this.m = make(map[string]string)
this.mu.Unlock()
}
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (this *StringStringMap) LockFunc(f func(m map[string]string)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringStringMap) RLockFunc(f func(m map[string]string)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -0,0 +1,30 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/container/gtree"
)
// Map based on red-black tree, alias of RedBlackTree.
type TreeMap = gtree.RedBlackTree
// NewTreeMap instantiates a tree map with the custom comparator.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewTreeMap(comparator func(v1, v2 interface{}) int, unsafe ...bool) *TreeMap {
return gtree.NewRedBlackTree(comparator, unsafe...)
}
// NewTreeMapFrom instantiates a tree map with the custom comparator and <data> map.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewTreeMapFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe ...bool) *TreeMap {
return gtree.NewRedBlackTreeFrom(comparator, data, unsafe...)
}

View File

@ -0,0 +1,125 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func getValue() interface{} {
return 3
}
func Test_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.New()
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_Map_Set_Fun(t *testing.T) {
m := gmap.New()
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_Map_Batch(t *testing.T) {
m := gmap.New()
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_Map_Iterator(t *testing.T) {
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewFrom(expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_Map_Lock(t *testing.T) {
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewFrom(expect)
m.LockFunc(func(m map[interface{}]interface{}) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[interface{}]interface{}) {
gtest.Assert(m, expect)
})
}
func Test_Map_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}
func Test_Map_Basic_Merge(t *testing.T) {
m1 := gmap.New()
m2 := gmap.New()
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[interface{}]interface{}{"key1": "val1", "key2": "val2"})
}

View File

@ -0,0 +1,55 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/util/gutil"
"testing"
)
var hashMap = gmap.New()
var listMap = gmap.NewListMap()
var treeMap = gmap.NewTreeMap(gutil.ComparatorInt)
func Benchmark_HashMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
hashMap.Set(i, i)
}
}
func Benchmark_ListMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
listMap.Set(i, i)
}
}
func Benchmark_TreeMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
treeMap.Set(i, i)
}
}
func Benchmark_HashMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
hashMap.Get(i)
}
}
func Benchmark_ListMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
listMap.Get(i)
}
}
func Benchmark_TreeMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
treeMap.Get(i)
}
}

View File

@ -0,0 +1,107 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"strconv"
"testing"
)
var ififm = gmap.New()
var iim = gmap.NewIntIntMap()
var iifm = gmap.NewIntAnyMap()
var ism = gmap.NewIntStrMap()
var sim = gmap.NewStrIntMap()
var sifm = gmap.NewStrAnyMap()
var ssm = gmap.NewStrStrMap()
func Benchmark_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iim.Set(i, i)
}
}
func Benchmark_IntAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iifm.Set(i, i)
}
}
func Benchmark_IntStrMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ism.Set(i, strconv.Itoa(i))
}
}
func Benchmark_AnyAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ififm.Set(i, i)
}
}
func Benchmark_StrIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sim.Set(strconv.Itoa(i), i)
}
}
func Benchmark_StrAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sifm.Set(strconv.Itoa(i), i)
}
}
func Benchmark_StrStrMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ssm.Set(strconv.Itoa(i), strconv.Itoa(i))
}
}
func Benchmark_IntIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iim.Get(i)
}
}
func Benchmark_IntAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iifm.Get(i)
}
}
func Benchmark_IntStrMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ism.Get(i)
}
}
func Benchmark_AnyAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ififm.Get(i)
}
}
func Benchmark_StrIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sim.Get(strconv.Itoa(i))
}
}
func Benchmark_StrAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sifm.Get(strconv.Itoa(i))
}
}
func Benchmark_StrStrMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ssm.Get(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,54 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"sync"
"testing"
)
var m1 = gmap.NewIntIntMap()
var m2 = sync.Map{}
func BenchmarkGmapSet(b *testing.B) {
for i := 0; i < b.N; i++ {
m1.Set(i, i)
}
}
func BenchmarkSyncmapSet(b *testing.B) {
for i := 0; i < b.N; i++ {
m2.Store(i, i)
}
}
func BenchmarkGmapGet(b *testing.B) {
for i := 0; i < b.N; i++ {
m1.Get(i)
}
}
func BenchmarkSyncmapGet(b *testing.B) {
for i := 0; i < b.N; i++ {
m2.Load(i)
}
}
func BenchmarkGmapRemove(b *testing.B) {
for i := 0; i < b.N; i++ {
m1.Remove(i)
}
}
func BenchmarkSyncmapRmove(b *testing.B) {
for i := 0; i < b.N; i++ {
m2.Delete(i)
}
}

View File

@ -0,0 +1,111 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"strconv"
"testing"
)
var ififmUnsafe = gmap.New(true)
var iimUnsafe = gmap.NewIntIntMap(true)
var iifmUnsafe = gmap.NewIntAnyMap(true)
var ismUnsafe = gmap.NewIntStrMap(true)
var simUnsafe = gmap.NewStrIntMap(true)
var sifmUnsafe = gmap.NewStrAnyMap(true)
var ssmUnsafe = gmap.NewStrStrMap(true)
// 写入性能测试
func Benchmark_Unsafe_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iimUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_IntAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iifmUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_IntStrMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ismUnsafe.Set(i, strconv.Itoa(i))
}
}
func Benchmark_Unsafe_AnyAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ififmUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_StrIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
simUnsafe.Set(strconv.Itoa(i), i)
}
}
func Benchmark_Unsafe_StrAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sifmUnsafe.Set(strconv.Itoa(i), i)
}
}
func Benchmark_Unsafe_StrStrMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ssmUnsafe.Set(strconv.Itoa(i), strconv.Itoa(i))
}
}
// 读取性能测试
func Benchmark_Unsafe_IntIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iimUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iifmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntStrMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ismUnsafe.Get(i)
}
}
func Benchmark_Unsafe_AnyAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ififmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_StrIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
simUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StrAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sifmUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StrStrMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ssmUnsafe.Get(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,70 @@
package gmap_test
import (
"fmt"
"github.com/gogf/gf/g/container/gmap"
)
func Example_Normal_Basic() {
m := gmap.New()
//Add data
m.Set("key1", "val1")
//Print size
fmt.Println(m.Size())
//output 1
add_map := make(map[interface{}]interface{})
add_map["key2"] = "val2"
add_map["key3"] = "val3"
add_map[1] = 1
fmt.Println(m.Values())
//Batch add data
m.Sets(add_map)
//Gets the value of the corresponding key
key3_val := m.Get("key3")
fmt.Println(key3_val)
//Get the value by key, or set it with given key-value if not exist.
get_or_set_val := m.GetOrSet("key4", "val4")
fmt.Println(get_or_set_val)
// Set key-value if the key does not exist, then return true; or else return false.
is_set := m.SetIfNotExist("key3", "val3")
fmt.Println(is_set)
//Remove key
m.Remove("key2")
fmt.Println(m.Keys())
//Batch remove keys
remove_keys := []interface{}{"key1", 1}
m.Removes(remove_keys)
fmt.Println(m.Keys())
//Contains checks whether a key exists.
is_contain := m.Contains("key3")
fmt.Println(is_contain)
//Flip exchanges key-value of the map, it will change key-value to value-key.
m.Flip()
fmt.Println(m.Map())
// Clear deletes all data of the map,
m.Clear()
fmt.Println(m.Size())
}
func Example_Normal_Merge() {
m1 := gmap.New()
m2 := gmap.New()
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(m2)
fmt.Println(m1.Map())
}

View File

@ -0,0 +1,130 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func getAny() interface{} {
return 123
}
func intAnyCallBack(int, interface{}) bool {
return true
}
func Test_IntAnyMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntAnyMap()
m.Set(1, 1)
gtest.Assert(m.Get(1), 1)
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet(2, "2"), "2")
gtest.Assert(m.SetIfNotExist(2, "2"), false)
gtest.Assert(m.SetIfNotExist(3, 3), true)
gtest.Assert(m.Remove(2), "2")
gtest.Assert(m.Contains(2), false)
gtest.AssertIN(3, m.Keys())
gtest.AssertIN(1, m.Keys())
gtest.AssertIN(3, m.Values())
gtest.AssertIN(1, m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]int{1: 1, 3: 3})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewIntAnyMapFrom(map[int]interface{}{1: 1, 2: "2"})
gtest.Assert(m2.Map(), map[int]interface{}{1: 1, 2: "2"})
})
}
func Test_IntAnyMap_Set_Fun(t *testing.T) {
m := gmap.NewIntAnyMap()
m.GetOrSetFunc(1, getAny)
m.GetOrSetFuncLock(2, getAny)
gtest.Assert(m.Get(1), 123)
gtest.Assert(m.Get(2), 123)
gtest.Assert(m.SetIfNotExistFunc(1, getAny), false)
gtest.Assert(m.SetIfNotExistFunc(3, getAny), true)
gtest.Assert(m.SetIfNotExistFuncLock(2, getAny), false)
gtest.Assert(m.SetIfNotExistFuncLock(4, getAny), true)
}
func Test_IntAnyMap_Batch(t *testing.T) {
m := gmap.NewIntAnyMap()
m.Sets(map[int]interface{}{1: 1, 2: "2", 3: 3})
gtest.Assert(m.Map(), map[int]interface{}{1: 1, 2: "2", 3: 3})
m.Removes([]int{1, 2})
gtest.Assert(m.Map(), map[int]interface{}{3: 3})
}
func Test_IntAnyMap_Iterator(t *testing.T) {
expect := map[int]interface{}{1: 1, 2: "2"}
m := gmap.NewIntAnyMapFrom(expect)
m.Iterator(func(k int, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k int, v interface{}) bool {
i++
return true
})
m.Iterator(func(k int, v interface{}) bool {
j++
return false
})
gtest.Assert(i, "2")
gtest.Assert(j, 1)
}
func Test_IntAnyMap_Lock(t *testing.T) {
expect := map[int]interface{}{1: 1, 2: "2"}
m := gmap.NewIntAnyMapFrom(expect)
m.LockFunc(func(m map[int]interface{}) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[int]interface{}) {
gtest.Assert(m, expect)
})
}
func Test_IntAnyMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewIntAnyMapFrom(map[int]interface{}{1: 1, 2: "2"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove(2)
//修改clone map,原 map 不影响
gtest.AssertIN(2, m.Keys())
}
func Test_IntAnyMap_Merge(t *testing.T) {
m1 := gmap.NewIntAnyMap()
m2 := gmap.NewIntAnyMap()
m1.Set(1, 1)
m2.Set(2, "2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[int]interface{}{1: 1, 2: "2"})
}

View File

@ -0,0 +1,131 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func getInt() int {
return 123
}
func intIntCallBack(int, int) bool {
return true
}
func Test_IntIntMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntIntMap()
m.Set(1, 1)
gtest.Assert(m.Get(1), 1)
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet(2, 2), 2)
gtest.Assert(m.SetIfNotExist(2, 2), false)
gtest.Assert(m.SetIfNotExist(3, 3), true)
gtest.Assert(m.Remove(2), 2)
gtest.Assert(m.Contains(2), false)
gtest.AssertIN(3, m.Keys())
gtest.AssertIN(1, m.Keys())
gtest.AssertIN(3, m.Values())
gtest.AssertIN(1, m.Values())
m.Flip()
gtest.Assert(m.Map(), map[int]int{1: 1, 3: 3})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewIntIntMapFrom(map[int]int{1: 1, 2: 2})
gtest.Assert(m2.Map(), map[int]int{1: 1, 2: 2})
})
}
func Test_IntIntMap_Set_Fun(t *testing.T) {
m := gmap.NewIntIntMap()
m.GetOrSetFunc(1, getInt)
m.GetOrSetFuncLock(2, getInt)
gtest.Assert(m.Get(1), 123)
gtest.Assert(m.Get(2), 123)
gtest.Assert(m.SetIfNotExistFunc(1, getInt), false)
gtest.Assert(m.SetIfNotExistFunc(3, getInt), true)
gtest.Assert(m.SetIfNotExistFuncLock(2, getInt), false)
gtest.Assert(m.SetIfNotExistFuncLock(4, getInt), true)
}
func Test_IntIntMap_Batch(t *testing.T) {
m := gmap.NewIntIntMap()
m.Sets(map[int]int{1: 1, 2: 2, 3: 3})
m.Iterator(intIntCallBack)
gtest.Assert(m.Map(), map[int]int{1: 1, 2: 2, 3: 3})
m.Removes([]int{1, 2})
gtest.Assert(m.Map(), map[int]int{3: 3})
}
func Test_IntIntMap_Iterator(t *testing.T) {
expect := map[int]int{1: 1, 2: 2}
m := gmap.NewIntIntMapFrom(expect)
m.Iterator(func(k int, v int) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k int, v int) bool {
i++
return true
})
m.Iterator(func(k int, v int) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_IntIntMap_Lock(t *testing.T) {
expect := map[int]int{1: 1, 2: 2}
m := gmap.NewIntIntMapFrom(expect)
m.LockFunc(func(m map[int]int) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[int]int) {
gtest.Assert(m, expect)
})
}
func Test_IntIntMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewIntIntMapFrom(map[int]int{1: 1, 2: 2})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove(2)
//修改clone map,原 map 不影响
gtest.AssertIN(2, m.Keys())
}
func Test_IntIntMap_Merge(t *testing.T) {
m1 := gmap.NewIntIntMap()
m2 := gmap.NewIntIntMap()
m1.Set(1, 1)
m2.Set(2, 2)
m1.Merge(m2)
gtest.Assert(m1.Map(), map[int]int{1: 1, 2: 2})
}

View File

@ -0,0 +1,135 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func getStr() string {
return "z"
}
func intStrCallBack(int, string) bool {
return true
}
func Test_IntStrMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntStrMap()
m.Set(1, "a")
gtest.Assert(m.Get(1), "a")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet(2, "b"), "b")
gtest.Assert(m.SetIfNotExist(2, "b"), false)
gtest.Assert(m.SetIfNotExist(3, "c"), true)
gtest.Assert(m.Remove(2), "b")
gtest.Assert(m.Contains(2), false)
gtest.AssertIN(3, m.Keys())
gtest.AssertIN(1, m.Keys())
gtest.AssertIN("a", m.Values())
gtest.AssertIN("c", m.Values())
//反转之后不成为以下 map,flip 操作只是翻转原 map
//gtest.Assert(m.Map(), map[string]int{"a": 1, "c": 3})
m_f := gmap.NewIntStrMap()
m_f.Set(1, "2")
m_f.Flip()
gtest.Assert(m_f.Map(), map[int]string{2: "1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewIntStrMapFrom(map[int]string{1: "a", 2: "b"})
gtest.Assert(m2.Map(), map[int]string{1: "a", 2: "b"})
})
}
func Test_IntStrMap_Set_Fun(t *testing.T) {
m := gmap.NewIntStrMap()
m.GetOrSetFunc(1, getStr)
m.GetOrSetFuncLock(2, getStr)
gtest.Assert(m.Get(1), "z")
gtest.Assert(m.Get(2), "z")
gtest.Assert(m.SetIfNotExistFunc(1, getStr), false)
gtest.Assert(m.SetIfNotExistFunc(3, getStr), true)
gtest.Assert(m.SetIfNotExistFuncLock(2, getStr), false)
gtest.Assert(m.SetIfNotExistFuncLock(4, getStr), true)
}
func Test_IntStrMap_Batch(t *testing.T) {
m := gmap.NewIntStrMap()
m.Sets(map[int]string{1: "a", 2: "b", 3: "c"})
gtest.Assert(m.Map(), map[int]string{1: "a", 2: "b", 3: "c"})
m.Removes([]int{1, 2})
gtest.Assert(m.Map(), map[int]interface{}{3: "c"})
}
func Test_IntStrMap_Iterator(t *testing.T) {
expect := map[int]string{1: "a", 2: "b"}
m := gmap.NewIntStrMapFrom(expect)
m.Iterator(func(k int, v string) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k int, v string) bool {
i++
return true
})
m.Iterator(func(k int, v string) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_IntStrMap_Lock(t *testing.T) {
expect := map[int]string{1: "a", 2: "b", 3: "c"}
m := gmap.NewIntStrMapFrom(expect)
m.LockFunc(func(m map[int]string) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[int]string) {
gtest.Assert(m, expect)
})
}
func Test_IntStrMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewIntStrMapFrom(map[int]string{1: "a", 2: "b", 3: "c"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove(2)
//修改clone map,原 map 不影响
gtest.AssertIN(2, m.Keys())
}
func Test_IntStrMap_Merge(t *testing.T) {
m1 := gmap.NewIntStrMap()
m2 := gmap.NewIntStrMap()
m1.Set(1, "a")
m2.Set(2, "b")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[int]string{1: "a", 2: "b"})
}

View File

@ -0,0 +1,120 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_List_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewListMap()
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewListMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_List_Map_Set_Fun(t *testing.T) {
m := gmap.NewListMap()
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_List_Map_Batch(t *testing.T) {
m := gmap.NewListMap()
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_List_Map_Iterator(t *testing.T) {
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewListMapFrom(expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_List_Map_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewListMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}
func Test_List_Map_Basic_Merge(t *testing.T) {
m1 := gmap.NewListMap()
m2 := gmap.NewListMap()
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[interface{}]interface{}{"key1": "val1", "key2": "val2"})
}
func Test_List_Map_Order(t *testing.T) {
m := gmap.NewListMap()
m.Set("k1", "v1")
m.Set("k2", "v2")
m.Set("k3", "v3")
gtest.Assert(m.Keys(), g.Slice{"k1", "k2", "k3"})
gtest.Assert(m.Values(), g.Slice{"v1", "v2", "v3"})
}

View File

@ -0,0 +1,128 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func stringAnyCallBack(string, interface{}) bool {
return true
}
func Test_StrAnyMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrAnyMap()
m.Set("a", 1)
gtest.Assert(m.Get("a"), 1)
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("b", "2"), "2")
gtest.Assert(m.SetIfNotExist("b", "2"), false)
gtest.Assert(m.SetIfNotExist("c", 3), true)
gtest.Assert(m.Remove("b"), "2")
gtest.Assert(m.Contains("b"), false)
gtest.AssertIN("c", m.Keys())
gtest.AssertIN("a", m.Keys())
gtest.AssertIN(3, m.Values())
gtest.AssertIN(1, m.Values())
m.Flip()
gtest.Assert(m.Map(), map[string]interface{}{"1": "a", "3": "c"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewStrAnyMapFrom(map[string]interface{}{"a": 1, "b": "2"})
gtest.Assert(m2.Map(), map[string]interface{}{"a": 1, "b": "2"})
})
}
func Test_StrAnyMap_Set_Fun(t *testing.T) {
m := gmap.NewStrAnyMap()
m.GetOrSetFunc("a", getAny)
m.GetOrSetFuncLock("b", getAny)
gtest.Assert(m.Get("a"), 123)
gtest.Assert(m.Get("b"), 123)
gtest.Assert(m.SetIfNotExistFunc("a", getAny), false)
gtest.Assert(m.SetIfNotExistFunc("c", getAny), true)
gtest.Assert(m.SetIfNotExistFuncLock("b", getAny), false)
gtest.Assert(m.SetIfNotExistFuncLock("d", getAny), true)
}
func Test_StrAnyMap_Batch(t *testing.T) {
m := gmap.NewStrAnyMap()
m.Sets(map[string]interface{}{"a": 1, "b": "2", "c": 3})
gtest.Assert(m.Map(), map[string]interface{}{"a": 1, "b": "2", "c": 3})
m.Removes([]string{"a", "b"})
gtest.Assert(m.Map(), map[string]interface{}{"c": 3})
}
func Test_StrAnyMap_Iterator(t *testing.T) {
expect := map[string]interface{}{"a": true, "b": false}
m := gmap.NewStrAnyMapFrom(expect)
m.Iterator(func(k string, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k string, v interface{}) bool {
i++
return true
})
m.Iterator(func(k string, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_StrAnyMap_Lock(t *testing.T) {
expect := map[string]interface{}{"a": true, "b": false}
m := gmap.NewStrAnyMapFrom(expect)
m.LockFunc(func(m map[string]interface{}) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[string]interface{}) {
gtest.Assert(m, expect)
})
}
func Test_StrAnyMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewStrAnyMapFrom(map[string]interface{}{"a": 1, "b": "2"})
m_clone := m.Clone()
m.Remove("a")
//修改原 map,clone 后的 map 不影响
gtest.AssertIN("a", m_clone.Keys())
m_clone.Remove("b")
//修改clone map,原 map 不影响
gtest.AssertIN("b", m.Keys())
}
func Test_StrAnyMap_Merge(t *testing.T) {
m1 := gmap.NewStrAnyMap()
m2 := gmap.NewStrAnyMap()
m1.Set("a", 1)
m2.Set("b", "2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[string]interface{}{"a": 1, "b": "2"})
}

View File

@ -0,0 +1,131 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func stringIntCallBack(string, int) bool {
return true
}
func Test_StrIntMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrIntMap()
m.Set("a", 1)
gtest.Assert(m.Get("a"), 1)
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("b", 2), 2)
gtest.Assert(m.SetIfNotExist("b", 2), false)
gtest.Assert(m.SetIfNotExist("c", 3), true)
gtest.Assert(m.Remove("b"), 2)
gtest.Assert(m.Contains("b"), false)
gtest.AssertIN("c", m.Keys())
gtest.AssertIN("a", m.Keys())
gtest.AssertIN(3, m.Values())
gtest.AssertIN(1, m.Values())
m_f := gmap.NewStrIntMap()
m_f.Set("1", 2)
m_f.Flip()
gtest.Assert(m_f.Map(), map[string]int{"2": 1})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewStrIntMapFrom(map[string]int{"a": 1, "b": 2})
gtest.Assert(m2.Map(), map[string]int{"a": 1, "b": 2})
})
}
func Test_StrIntMap_Set_Fun(t *testing.T) {
m := gmap.NewStrIntMap()
m.GetOrSetFunc("a", getInt)
m.GetOrSetFuncLock("b", getInt)
gtest.Assert(m.Get("a"), 123)
gtest.Assert(m.Get("b"), 123)
gtest.Assert(m.SetIfNotExistFunc("a", getInt), false)
gtest.Assert(m.SetIfNotExistFunc("c", getInt), true)
gtest.Assert(m.SetIfNotExistFuncLock("b", getInt), false)
gtest.Assert(m.SetIfNotExistFuncLock("d", getInt), true)
}
func Test_StrIntMap_Batch(t *testing.T) {
m := gmap.NewStrIntMap()
m.Sets(map[string]int{"a": 1, "b": 2, "c": 3})
gtest.Assert(m.Map(), map[string]int{"a": 1, "b": 2, "c": 3})
m.Removes([]string{"a", "b"})
gtest.Assert(m.Map(), map[string]int{"c": 3})
}
func Test_StrIntMap_Iterator(t *testing.T) {
expect := map[string]int{"a": 1, "b": 2}
m := gmap.NewStrIntMapFrom(expect)
m.Iterator(func(k string, v int) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k string, v int) bool {
i++
return true
})
m.Iterator(func(k string, v int) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_StrIntMap_Lock(t *testing.T) {
expect := map[string]int{"a": 1, "b": 2}
m := gmap.NewStrIntMapFrom(expect)
m.LockFunc(func(m map[string]int) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[string]int) {
gtest.Assert(m, expect)
})
}
func Test_StrIntMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewStrIntMapFrom(map[string]int{"a": 1, "b": 2, "c": 3})
m_clone := m.Clone()
m.Remove("a")
//修改原 map,clone 后的 map 不影响
gtest.AssertIN("a", m_clone.Keys())
m_clone.Remove("b")
//修改clone map,原 map 不影响
gtest.AssertIN("b", m.Keys())
}
func Test_StrIntMap_Merge(t *testing.T) {
m1 := gmap.NewStrIntMap()
m2 := gmap.NewStrIntMap()
m1.Set("a", 1)
m2.Set("b", 2)
m1.Merge(m2)
gtest.Assert(m1.Map(), map[string]int{"a": 1, "b": 2})
}

View File

@ -0,0 +1,128 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func stringStrCallBack(string, string) bool {
return true
}
func Test_StrStrMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrStrMap()
m.Set("a", "a")
gtest.Assert(m.Get("a"), "a")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("b", "b"), "b")
gtest.Assert(m.SetIfNotExist("b", "b"), false)
gtest.Assert(m.SetIfNotExist("c", "c"), true)
gtest.Assert(m.Remove("b"), "b")
gtest.Assert(m.Contains("b"), false)
gtest.AssertIN("c", m.Keys())
gtest.AssertIN("a", m.Keys())
gtest.AssertIN("a", m.Values())
gtest.AssertIN("c", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[string]string{"a": "a", "c": "c"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewStrStrMapFrom(map[string]string{"a": "a", "b": "b"})
gtest.Assert(m2.Map(), map[string]string{"a": "a", "b": "b"})
})
}
func Test_StrStrMap_Set_Fun(t *testing.T) {
m := gmap.NewStrStrMap()
m.GetOrSetFunc("a", getStr)
m.GetOrSetFuncLock("b", getStr)
gtest.Assert(m.Get("a"), "z")
gtest.Assert(m.Get("b"), "z")
gtest.Assert(m.SetIfNotExistFunc("a", getStr), false)
gtest.Assert(m.SetIfNotExistFunc("c", getStr), true)
gtest.Assert(m.SetIfNotExistFuncLock("b", getStr), false)
gtest.Assert(m.SetIfNotExistFuncLock("d", getStr), true)
}
func Test_StrStrMap_Batch(t *testing.T) {
m := gmap.NewStrStrMap()
m.Sets(map[string]string{"a": "a", "b": "b", "c": "c"})
gtest.Assert(m.Map(), map[string]string{"a": "a", "b": "b", "c": "c"})
m.Removes([]string{"a", "b"})
gtest.Assert(m.Map(), map[string]string{"c": "c"})
}
func Test_StrStrMap_Iterator(t *testing.T) {
expect := map[string]string{"a": "a", "b": "b"}
m := gmap.NewStrStrMapFrom(expect)
m.Iterator(func(k string, v string) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k string, v string) bool {
i++
return true
})
m.Iterator(func(k string, v string) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_StrStrMap_Lock(t *testing.T) {
expect := map[string]string{"a": "a", "b": "b"}
m := gmap.NewStrStrMapFrom(expect)
m.LockFunc(func(m map[string]string) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[string]string) {
gtest.Assert(m, expect)
})
}
func Test_StrStrMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewStrStrMapFrom(map[string]string{"a": "a", "b": "b", "c": "c"})
m_clone := m.Clone()
m.Remove("a")
//修改原 map,clone 后的 map 不影响
gtest.AssertIN("a", m_clone.Keys())
m_clone.Remove("b")
//修改clone map,原 map 不影响
gtest.AssertIN("b", m.Keys())
}
func Test_StrStrMap_Merge(t *testing.T) {
m1 := gmap.NewStrStrMap()
m2 := gmap.NewStrStrMap()
m1.Set("a", "a")
m2.Set("b", "b")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[string]string{"a": "a", "b": "b"})
}

View File

@ -0,0 +1,102 @@
// Copyright 2017-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 (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gutil"
"testing"
)
func Test_Tree_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewTreeMap(gutil.ComparatorString)
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewTreeMapFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_Tree_Map_Set_Fun(t *testing.T) {
m := gmap.NewTreeMap(gutil.ComparatorString)
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_Tree_Map_Batch(t *testing.T) {
m := gmap.NewTreeMap(gutil.ComparatorString)
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_Tree_Map_Iterator(t *testing.T) {
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewTreeMapFrom(gutil.ComparatorString, expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_Tree_Map_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewTreeMapFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}

View File

@ -1,125 +1,144 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// Package gpool provides a object-reusable concurrent-safe pool.
//
// 对象复用池.
// Package gpool provides object-reusable concurrent-safe pool.
package gpool
import (
"errors"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/os/gtimer"
"time"
"errors"
"time"
"github.com/gogf/gf/g/container/glist"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gtimer"
)
// 对象池
// Object-Reusable Pool.
type Pool struct {
list *glist.List // 可用/闲置的文件指针链表
closed *gtype.Bool // 连接池是否已关闭
Expire int64 // (毫秒)闲置最大时间,超过该时间则被系统回收
NewFunc func()(interface{}, error) // 创建对象的方法定义
ExpireFunc func(interface{}) // 对象的过期销毁方法(当池对象销毁需要执行额外的销毁操作时,需要定义该方法)
// 例如: net.Conn, os.File等对象都需要执行额外关闭操作
list *glist.List // Available/idle list.
closed *gtype.Bool // Whether the pool is closed.
Expire int64 // Max idle time(ms), after which it is recycled.
NewFunc func() (interface{}, error) // Callback function to create item.
ExpireFunc func(interface{}) // Expired destruction function for objects.
// This function needs to be defined when the pool object
// needs to perform additional destruction operations.
// Eg: net.Conn, os.File, etc.
}
// 对象池数据项
// Pool item.
type poolItem struct {
expire int64 // (毫秒)过期时间
value interface{} // 对象值
expire int64 // Expire time(millisecond).
value interface{} // Value.
}
// 对象创建方法类型
type NewFunc func() (interface{}, error)
// Creation function for object.
type NewFunc func() (interface{}, error)
// 对象过期方法类型
// Destruction function for object.
type ExpireFunc func(interface{})
// 创建一个对象池,为保证执行效率,过期时间一旦设定之后无法修改
// expire = 0表示不过期expire < 0表示使用完立即回收expire > 0表示超时回收
// 注意过期时间单位为**毫秒**
func New(expire int, newFunc NewFunc, expireFunc...ExpireFunc) *Pool {
r := &Pool {
list : glist.New(),
closed : gtype.NewBool(),
Expire : int64(expire),
NewFunc : newFunc,
}
if len(expireFunc) > 0 {
r.ExpireFunc = expireFunc[0]
}
gtimer.AddSingleton(time.Second, r.checkExpire)
return r
// New returns a new object pool.
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
//
// Expiration logistics:
// expire = 0 : not expired;
// expire < 0 : immediate expired after use;
// expire > 0 : timeout expired;
// Note that the expiration time unit is ** milliseconds **.
func New(expire int, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
r := &Pool{
list: glist.New(),
closed: gtype.NewBool(),
Expire: int64(expire),
NewFunc: newFunc,
}
if len(expireFunc) > 0 {
r.ExpireFunc = expireFunc[0]
}
gtimer.AddSingleton(time.Second, r.checkExpire)
return r
}
// 放一个临时对象到池中
// Put puts an item to pool.
func (p *Pool) Put(value interface{}) {
item := &poolItem {
value : value,
}
if p.Expire == 0 {
item.expire = 0
} else {
item.expire = gtime.Millisecond() + p.Expire
}
p.list.PushBack(item)
item := &poolItem{
value: value,
}
if p.Expire == 0 {
item.expire = 0
} else {
item.expire = gtime.Millisecond() + p.Expire
}
p.list.PushBack(item)
}
// 清空对象池
// Clear clears pool, which means it will remove all items from pool.
func (p *Pool) Clear() {
p.list.RemoveAll()
p.list.RemoveAll()
}
// 从池中获得一个临时对象
// Get picks an item from pool.
func (p *Pool) Get() (interface{}, error) {
for !p.closed.Val() {
if r := p.list.PopFront(); r != nil {
f := r.(*poolItem)
if f.expire == 0 || f.expire > gtime.Millisecond() {
return f.value, nil
}
} else {
break
}
}
if p.NewFunc != nil {
return p.NewFunc()
}
return nil, errors.New("pool is empty")
for !p.closed.Val() {
if r := p.list.PopFront(); r != nil {
f := r.(*poolItem)
if f.expire == 0 || f.expire > gtime.Millisecond() {
return f.value, nil
}
} else {
break
}
}
if p.NewFunc != nil {
return p.NewFunc()
}
return nil, errors.New("pool is empty")
}
// 查询当前池中的对象数量
// Size returns the count of available items of pool.
func (p *Pool) Size() int {
return p.list.Len()
return p.list.Len()
}
// 关闭池
// Close closes the pool. If <p> has ExpireFunc,
// then it automatically closes all items using this function before it's closed.
func (p *Pool) Close() {
p.closed.Set(true)
p.closed.Set(true)
}
// 超时检测循环
// checkExpire removes expired items from pool every second.
func (p *Pool) checkExpire() {
if p.closed.Val() {
gtimer.Exit()
}
for {
if r := p.list.PopFront(); r != nil {
item := r.(*poolItem)
if item.expire == 0 || item.expire > gtime.Millisecond() {
p.list.PushFront(item)
break
}
if p.ExpireFunc != nil {
p.ExpireFunc(item.value)
}
} else {
break
}
}
}
if p.closed.Val() {
// If p has ExpireFunc,
// then it must close all items using this function.
if p.ExpireFunc != nil {
for {
if r := p.list.PopFront(); r != nil {
p.ExpireFunc(r.(*poolItem).value)
} else {
break
}
}
}
gtimer.Exit()
}
for {
// TODO Do not use Pop and Push mechanism, which is not graceful.
if r := p.list.PopFront(); r != nil {
item := r.(*poolItem)
if item.expire == 0 || item.expire > gtime.Millisecond() {
p.list.PushFront(item)
break
}
if p.ExpireFunc != nil {
p.ExpireFunc(item.value)
}
} else {
break
}
}
}

View File

@ -0,0 +1,42 @@
// 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.
// go test *.go -bench=".*"
package gpool_test
import (
"github.com/gogf/gf/g/container/gpool"
"sync"
"testing"
)
var pool = gpool.New(99999999, nil)
var syncp = sync.Pool{}
func BenchmarkGPoolPut(b *testing.B) {
for i := 0; i < b.N; i++ {
pool.Put(i)
}
}
func BenchmarkGPoolGet(b *testing.B) {
for i := 0; i < b.N; i++ {
pool.Get()
}
}
func BenchmarkSyncPoolPut(b *testing.B) {
for i := 0; i < b.N; i++ {
syncp.Put(i)
}
}
func BenchmarkGpoolGet(b *testing.B) {
for i := 0; i < b.N; i++ {
syncp.Get()
}
}

View File

@ -1,41 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*"
package gpool
import (
"testing"
"sync"
)
var pool = New(99999999, nil)
var syncp = sync.Pool{}
func BenchmarkGPoolPut(b *testing.B) {
for i := 0; i < b.N; i++ {
pool.Put(i)
}
}
func BenchmarkGPoolGet(b *testing.B) {
for i := 0; i < b.N; i++ {
pool.Get()
}
}
func BenchmarkSyncPoolPut(b *testing.B) {
for i := 0; i < b.N; i++ {
syncp.Put(i)
}
}
func BenchmarkGpoolGet(b *testing.B) {
for i := 0; i < b.N; i++ {
syncp.Get()
}
}

View File

@ -0,0 +1,92 @@
package gpool_test
import (
"errors"
"testing"
"time"
"github.com/gogf/gf/g/container/gpool"
"github.com/gogf/gf/g/test/gtest"
)
var nf gpool.NewFunc = func() (i interface{}, e error) {
return "hello", nil
}
var assertIndex int = 0
var ef gpool.ExpireFunc = func(i interface{}) {
assertIndex++
gtest.Assert(i, assertIndex)
}
func Test_Gpool(t *testing.T) {
gtest.Case(t, func() {
//
//expire = 0
p1 := gpool.New(0, nf)
p1.Put(1)
p1.Put(2)
time.Sleep(1 * time.Second)
//test won't be timeout
v1, err1 := p1.Get()
gtest.Assert(err1, nil)
gtest.Assert(v1, 1)
//test clear
p1.Clear()
gtest.Assert(p1.Size(), 0)
//test newFunc
v1, err1 = p1.Get()
gtest.Assert(err1, nil)
gtest.Assert(v1, "hello")
//put data again
p1.Put(3)
p1.Put(4)
v1, err1 = p1.Get()
gtest.Assert(err1, nil)
gtest.Assert(v1, 3)
//test close
p1.Close()
v1, err1 = p1.Get()
gtest.Assert(err1, nil)
gtest.Assert(v1, "hello")
})
gtest.Case(t, func() {
//
//expire > 0
p2 := gpool.New(2000, nil, ef)
for index := 0; index < 10; index++ {
p2.Put(index)
}
gtest.Assert(p2.Size(), 10)
v2, err2 := p2.Get()
gtest.Assert(err2, nil)
gtest.Assert(v2, 0)
//test timeout expireFunc
time.Sleep(3 * time.Second)
v2, err2 = p2.Get()
gtest.Assert(err2, errors.New("pool is empty"))
gtest.Assert(v2, nil)
//test close expireFunc
for index := 0; index < 10; index++ {
p2.Put(index)
}
gtest.Assert(p2.Size(), 10)
v2, err2 = p2.Get()
gtest.Assert(err2, nil)
gtest.Assert(v2, 0)
assertIndex = 0
p2.Close()
time.Sleep(3 * time.Second)
})
gtest.Case(t, func() {
//
//expire < 0
p3 := gpool.New(-1, nil)
v3, err3 := p3.Get()
gtest.Assert(err3, errors.New("pool is empty"))
gtest.Assert(v3, nil)
})
}

View File

@ -1,120 +1,139 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// Package gqueue provides a dynamic/static concurrent-safe(alternative) queue.
// Package gqueue provides a dynamic/static concurrent-safe queue.
//
// 并发安全的动态队列.
// Features:
//
// 1. FIFO queue(data -> list -> chan);
//
// 2. Fast creation and initialization;
//
// 3. Support dynamic queue size(unlimited queue size);
//
// 4. Blocking when reading data from queue;
//
// 特点:
// 1. 动态队列初始化速度快;
// 2. 动态的队列大小(不限大小)
// 3. 取数据时如果队列为空那么会阻塞等待;
package gqueue
import (
"container/list"
"math"
"sync"
"github.com/gogf/gf/g/container/glist"
"github.com/gogf/gf/g/container/gtype"
"math"
)
// 1、这是一个先进先出的队列(chan <-- list)
//
// 2、当创建Queue对象时限定大小那么等同于一个同步的chan并发安全队列
//
// 3、不限制大小时list链表用以存储数据临时chan负责为客户端读取数据当从chan获取数据时list往chan中不停补充数据
//
// 4、由于功能主体是chan那么操作仍然像chan那样具有阻塞效果
type Queue struct {
mu sync.Mutex // 底层链表写锁
limit int // 队列限制大小
list *list.List // 底层数据链表
events chan struct{} // 写入事件通知
closed chan struct{} // 队列关闭通知
C chan interface{} // 队列数据读取
limit int // Limit for queue size.
list *glist.List // Underlying list structure for data maintaining.
closed *gtype.Bool // Whether queue is closed.
events chan struct{} // Events for data writing.
C chan interface{} // Underlying channel for data reading.
}
const (
// 动态队列缓冲区大小
gDEFAULT_QUEUE_SIZE = 10000
// Size for queue buffer.
gDEFAULT_QUEUE_SIZE = 10000
// Max batch size per-fetching from list.
gDEFAULT_MAX_BATCH_SIZE = 10
)
// 队列大小为非必须参数,默认不限制
func New(limit...int) *Queue {
q := &Queue {
closed : make(chan struct{}, 0),
}
if len(limit) > 0 {
q.limit = limit[0]
q.C = make(chan interface{}, limit[0])
} else {
q.list = list.New()
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
go q.startAsyncLoop()
}
return q
// New returns an empty queue object.
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited in default.
// When <limit> is given, the queue will be static and high performance which is comparable with stdlib channel.
func New(limit ...int) *Queue {
q := &Queue{
closed: gtype.NewBool(),
}
if len(limit) > 0 {
q.limit = limit[0]
q.C = make(chan interface{}, limit[0])
} else {
q.list = glist.New()
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
go q.startAsyncLoop()
}
return q
}
// 异步list->chan同步队列
// startAsyncLoop starts an asynchronous goroutine,
// which handles the data synchronization from list <q.list> to channel <q.C>.
func (q *Queue) startAsyncLoop() {
for {
select {
case <- q.closed:
return
case <- q.events:
for {
if length := q.list.Len(); length > 0 {
array := make([]interface{}, length)
q.mu.Lock()
for i := 0; i < length; i++ {
if e := q.list.Front(); e != nil {
array[i] = q.list.Remove(e)
} else {
break
}
}
q.mu.Unlock()
for _, v := range array {
q.C <- v
}
} else {
break
}
}
}
}
defer func() {
if q.closed.Val() {
_ = recover()
}
}()
for !q.closed.Val() {
<-q.events
for !q.closed.Val() {
if length := q.list.Len(); length > 0 {
if length > gDEFAULT_MAX_BATCH_SIZE {
length = gDEFAULT_MAX_BATCH_SIZE
}
for _, v := range q.list.PopFronts(length) {
// When q.C is closed, it will panic here, especially q.C is being blocked for writing.
// If any error occurs here, it will be caught by recover and be ignored.
q.C <- v
}
} else {
break
}
}
// Clear q.events to remain just one event to do the next synchronization check.
for i := 0; i < len(q.events)-1; i++ {
<-q.events
}
}
// It should be here to close q.C.
// It's the sender's responsibility to close channel when it should be closed.
close(q.C)
}
// 将数据压入队列, 队尾
// Push pushes the data <v> into the queue.
// Note that it would panics if Push is called after the queue is closed.
func (q *Queue) Push(v interface{}) {
if q.limit > 0 {
q.C <- v
} else {
q.mu.Lock()
q.list.PushBack(v)
q.mu.Unlock()
q.events <- struct{}{}
}
if q.limit > 0 {
q.C <- v
} else {
q.list.PushBack(v)
if len(q.events) < gDEFAULT_QUEUE_SIZE {
q.events <- struct{}{}
}
}
}
// 从队头先进先出地从队列取出一项数据
// Pop pops an item from the queue in FIFO way.
// Note that it would return nil immediately if Pop is called after the queue is closed.
func (q *Queue) Pop() interface{} {
return <- q.C
return <-q.C
}
// 关闭队列(通知所有通过Pop*阻塞的协程退出)
// Close closes the queue.
// Notice: It would notify all goroutines return immediately,
// which are being blocked reading using Pop method.
func (q *Queue) Close() {
close(q.C)
close(q.events)
close(q.closed)
q.closed.Set(true)
if q.events != nil {
close(q.events)
}
for i := 0; i < gDEFAULT_MAX_BATCH_SIZE; i++ {
q.Pop()
}
}
// 获取当前队列大小
// Len returns the length of the queue.
func (q *Queue) Len() (length int) {
if q.list != nil {
length += q.list.Len()
}
length += len(q.C)
return
}
// Size is alias of Len.
func (q *Queue) Size() int {
return len(q.C) + q.list.Len()
return q.Len()
}

View File

@ -1,50 +1,50 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gqueue_test
import (
"testing"
"gitee.com/johng/gf/g/container/gqueue"
"github.com/gogf/gf/g/container/gqueue"
"testing"
)
var bn = 20000000
var length = 1000000
var qstatic = gqueue.New(length)
var qdynamic = gqueue.New()
var cany = make(chan interface{}, length)
var bn = 20000000
var length = 1000000
var qstatic = gqueue.New(length)
var qdynamic = gqueue.New()
var cany = make(chan interface{}, length)
func Benchmark_Gqueue_StaticPushAndPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qstatic.Push(i)
qstatic.Pop()
}
b.N = bn
for i := 0; i < b.N; i++ {
qstatic.Push(i)
qstatic.Pop()
}
}
func Benchmark_Gqueue_DynamicPush(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qdynamic.Push(i)
}
b.N = bn
for i := 0; i < b.N; i++ {
qdynamic.Push(i)
}
}
func Benchmark_Gqueue_DynamicPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qdynamic.Pop()
}
b.N = bn
for i := 0; i < b.N; i++ {
qdynamic.Pop()
}
}
func Benchmark_Channel_PushAndPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
cany <- i
<- cany
}
b.N = bn
for i := 0; i < b.N; i++ {
cany <- i
<-cany
}
}

View File

@ -0,0 +1,54 @@
// 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.
// go test *.go -bench=".*" -benchmem
package gqueue_test
import (
"github.com/gogf/gf/g/container/gqueue"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func TestQueue_Len(t *testing.T) {
max := 100
for n := 10; n < max; n++ {
q1 := gqueue.New(max)
for i := 0; i < max; i++ {
q1.Push(i)
}
gtest.Assert(q1.Len(), max)
gtest.Assert(q1.Size(), max)
}
}
func TestQueue_Basic(t *testing.T) {
q := gqueue.New()
for i := 0; i < 100; i++ {
q.Push(i)
}
gtest.Assert(q.Pop(), 0)
gtest.Assert(q.Pop(), 1)
}
func TestQueue_Pop(t *testing.T) {
q1 := gqueue.New()
q1.Push(1)
q1.Push(2)
q1.Push(3)
q1.Push(4)
i1 := q1.Pop()
gtest.Assert(i1, 1)
}
func TestQueue_Close(t *testing.T) {
q1 := gqueue.New()
q1.Push(1)
q1.Push(2)
gtest.Assert(q1.Len(), 2)
q1.Close()
}

View File

@ -1,236 +1,258 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// Package gring provides a concurrent-safe(alternative) ring(circular lists).
//
// 并发安全环.
// Package gring provides a concurrent-safe/unsafe ring(circular lists).
package gring
import (
"container/ring"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/internal/rwmutex"
"container/ring"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
)
type Ring struct {
mu *rwmutex.RWMutex // 互斥锁
ring *ring.Ring // 底层环形数据结构
len *gtype.Int // 数据大小(已使用的大小)
cap *gtype.Int // 总长度(分配的环大小,包括未使用的数据项数量)
dirty *gtype.Bool // 标记环是否脏了(需要重新计算大小,当环大小发生改变时做标记)
mu *rwmutex.RWMutex
ring *ring.Ring // Underlying ring.
len *gtype.Int // Length(already used size).
cap *gtype.Int // Capability(>=len).
dirty *gtype.Bool // Dirty, which means the len and cap should be recalculated.
// It's marked dirty when the size of ring changes.
}
func New(cap int, unsafe...bool) *Ring {
return &Ring {
mu : rwmutex.New(unsafe...),
ring : ring.New(cap),
len : gtype.NewInt(),
cap : gtype.NewInt(cap),
dirty : gtype.NewBool(),
}
func New(cap int, unsafe ...bool) *Ring {
return &Ring{
mu: rwmutex.New(unsafe...),
ring: ring.New(cap),
len: gtype.NewInt(),
cap: gtype.NewInt(cap),
dirty: gtype.NewBool(),
}
}
// 返回当前环指向的数据项值
// Val returns the item's value of current position.
func (r *Ring) Val() interface{} {
r.mu.RLock()
v := r.ring.Value
r.mu.RUnlock()
return v
r.mu.RLock()
v := r.ring.Value
r.mu.RUnlock()
return v
}
// 返回当前环已有数据项大小
// Len returns the size of ring.
func (r *Ring) Len() int {
r.checkAndUpdateLenAndCap()
return r.len.Val()
r.checkAndUpdateLenAndCap()
return r.len.Val()
}
// 返回当前环总大小(包含未使用长度)
// Cap returns the capacity of ring.
func (r *Ring) Cap() int {
r.checkAndUpdateLenAndCap()
return r.cap.Val()
r.checkAndUpdateLenAndCap()
return r.cap.Val()
}
// 检测并执行len和cap的更新(两者必须一起更新)
func (r *Ring) checkAndUpdateLenAndCap() {
if !r.dirty.Val() {
return
}
totalLen := 0
emptyLen := 0
if r.ring != nil {
r.mu.RLock()
for p := r.ring.Next(); p != r.ring; p = p.Next() {
if p.Value == nil {
emptyLen++
}
totalLen++
}
r.mu.RUnlock()
}
r.cap.Set(totalLen)
r.len.Set(totalLen - emptyLen)
r.dirty.Set(false)
// Checks and updates the len and cap of ring when ring is dirty.
func (r *Ring) checkAndUpdateLenAndCap() {
if !r.dirty.Val() {
return
}
totalLen := 0
emptyLen := 0
if r.ring != nil {
r.mu.RLock()
for p := r.ring.Next(); p != r.ring; p = p.Next() {
if p.Value == nil {
emptyLen++
}
totalLen++
}
r.mu.RUnlock()
}
r.cap.Set(totalLen)
r.len.Set(totalLen - emptyLen)
r.dirty.Set(false)
}
// 当前位置设置数据项值
// Set sets value to the item of current position.
func (r *Ring) Set(value interface{}) *Ring {
r.mu.Lock()
if r.ring.Value == nil {
r.len.Add(1)
}
r.ring.Value = value
r.mu.Unlock()
return r
r.mu.Lock()
if r.ring.Value == nil {
r.len.Add(1)
}
r.ring.Value = value
r.mu.Unlock()
return r
}
// Set & Next
// Put sets <value> to current item of ring and moves position to next item.
func (r *Ring) Put(value interface{}) *Ring {
r.mu.Lock()
if r.ring.Value == nil {
r.len.Add(1)
}
r.ring.Value = value
r.ring = r.ring.Next()
r.mu.Unlock()
return r
r.mu.Lock()
if r.ring.Value == nil {
r.len.Add(1)
}
r.ring.Value = value
r.ring = r.ring.Next()
r.mu.Unlock()
return r
}
// 环往后(n > 0)或者往前(n < 0)移动n个元素
// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0)
// in the ring and returns that ring element. r must not be empty.
func (r *Ring) Move(n int) *Ring {
r.mu.Lock()
r.ring = r.ring.Move(n)
r.mu.Unlock()
return r
r.mu.Lock()
r.ring = r.ring.Move(n)
r.mu.Unlock()
return r
}
// 环往前移动1个元素
// Prev returns the previous ring element. r must not be empty.
func (r *Ring) Prev() *Ring {
r.mu.Lock()
r.ring = r.ring.Prev()
r.mu.Unlock()
return r
r.mu.Lock()
r.ring = r.ring.Prev()
r.mu.Unlock()
return r
}
// 环往后移动1个元素
// Next returns the next ring element. r must not be empty.
func (r *Ring) Next() *Ring {
r.mu.Lock()
r.ring = r.ring.Next()
r.mu.Unlock()
return r
r.mu.Lock()
r.ring = r.ring.Next()
r.mu.Unlock()
return r
}
// 连接两个环,两个环的大小和位置都有可能会发生改变。
// 1、链接将环r与环s连接使得r.Next()成为s并返回r.Next()的原始值。r一定不能为空。
// 2、如果r和s指向同一个环则链接它们会从环中移除r和s之间的元素。
// 删除的元素形成子环,结果是对该子环的引用(如果没有删除元素结果仍然是r.Next()的原始值而不是nil)。
// 3、如果r和s指向不同的环则链接它们会创建一个单独的环并在r之后插入s的元素。 结果指向插入后s的最后一个元素后面的元素。
// Link connects ring r with ring s such that r.Next()
// becomes s and returns the original value for r.Next().
// r must not be empty.
//
// If r and s point to the same ring, linking
// them removes the elements between r and s from the ring.
// The removed elements form a subring and the result is a
// reference to that subring (if no elements were removed,
// the result is still the original value for r.Next(),
// and not nil).
//
// If r and s point to different rings, linking
// them creates a single ring with the elements of s inserted
// after r. The result points to the element following the
// last element of s after insertion.
//
func (r *Ring) Link(s *Ring) *Ring {
r.mu.Lock()
s.mu.Lock()
r.ring.Link(s.ring)
s.mu.Unlock()
r.mu.Unlock()
r.dirty.Set(true)
s.dirty.Set(true)
return r
r.mu.Lock()
s.mu.Lock()
r.ring.Link(s.ring)
s.mu.Unlock()
r.mu.Unlock()
r.dirty.Set(true)
s.dirty.Set(true)
return r
}
// 删除环中当前位置往后的n个数据项
// Unlink removes n % r.Len() elements from the ring r, starting
// at r.Next(). If n % r.Len() == 0, r remains unchanged.
// The result is the removed subring. r must not be empty.
//
func (r *Ring) Unlink(n int) *Ring {
r.mu.Lock()
r.ring = r.ring.Unlink(n)
r.dirty.Set(true)
r.mu.Unlock()
return r
r.mu.Lock()
r.ring = r.ring.Unlink(n)
r.dirty.Set(true)
r.mu.Unlock()
return r
}
// 读锁遍历往后只读遍历回调函数返回true表示继续遍历否则退出遍历
// RLockIteratorNext iterates and locks reading forward
// with given callback function <f> within RWMutex.RLock.
// If <f> returns true, then it continues iterating; or false to stop.
func (r *Ring) RLockIteratorNext(f func(value interface{}) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
if !f(r.ring.Value) {
return
}
for p := r.ring.Next(); p != r.ring; p = p.Next() {
if !f(p.Value) {
break
}
}
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring.Value) {
return
}
for p := r.ring.Next(); p != r.ring; p = p.Next() {
if !f(p.Value) {
break
}
}
}
// 读锁遍历往前只读遍历回调函数返回true表示继续遍历否则退出遍历
// RLockIteratorPrev iterates and locks reading backward
// with given callback function <f> within RWMutex.RLock.
// If <f> returns true, then it continues iterating; or false to stop.
func (r *Ring) RLockIteratorPrev(f func(value interface{}) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
if !f(r.ring.Value) {
return
}
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
if !f(p.Value) {
break
}
}
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring.Value) {
return
}
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
if !f(p.Value) {
break
}
}
}
// 写锁遍历往后写遍历回调函数返回true表示继续遍历否则退出遍历
// LockIteratorNext iterates and locks writing forward
// with given callback function <f> within RWMutex.RLock.
// If <f> returns true, then it continues iterating; or false to stop.
func (r *Ring) LockIteratorNext(f func(item *ring.Ring) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
if !f(r.ring) {
return
}
for p := r.ring.Next(); p != r.ring; p = p.Next() {
if !f(p) {
break
}
}
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring) {
return
}
for p := r.ring.Next(); p != r.ring; p = p.Next() {
if !f(p) {
break
}
}
}
// 写锁遍历往前写遍历回调函数返回true表示继续遍历否则退出遍历
// LockIteratorPrev iterates and locks writing backward
// with given callback function <f> within RWMutex.RLock.
// If <f> returns true, then it continues iterating; or false to stop.
func (r *Ring) LockIteratorPrev(f func(item *ring.Ring) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
if !f(r.ring) {
return
}
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
if !f(p) {
break
}
}
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring) {
return
}
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
if !f(p) {
break
}
}
}
// 从当前位置,往后只读完整遍历,返回非空数据项值构成的数组
// SliceNext returns a copy of all item values as slice forward from current position.
func (r *Ring) SliceNext() []interface{} {
s := make([]interface{}, 0)
r.mu.RLock()
if r.ring.Value != nil {
s = append(s, r.ring.Value)
}
for p := r.ring.Next(); p != r.ring; p = p.Next() {
if p.Value != nil {
s = append(s, p.Value)
}
}
r.mu.RUnlock()
return s
s := make([]interface{}, 0)
r.mu.RLock()
if r.ring.Value != nil {
s = append(s, r.ring.Value)
}
for p := r.ring.Next(); p != r.ring; p = p.Next() {
if p.Value != nil {
s = append(s, p.Value)
}
}
r.mu.RUnlock()
return s
}
// 从当前位置,往前只读完整遍历,返回非空数据项值构成的数组
// SlicePrev returns a copy of all item values as slice backward from current position.
func (r *Ring) SlicePrev() []interface{} {
s := make([]interface{}, 0)
r.mu.RLock()
if r.ring.Value != nil {
s = append(s, r.ring.Value)
}
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
if p.Value != nil {
s = append(s, p.Value)
}
}
r.mu.RUnlock()
return s
}
s := make([]interface{}, 0)
r.mu.RLock()
if r.ring.Value != nil {
s = append(s, r.ring.Value)
}
for p := r.ring.Prev(); p != r.ring; p = p.Prev() {
if p.Value != nil {
s = append(s, p.Value)
}
}
r.mu.RUnlock()
return s
}

View File

@ -1,46 +1,46 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"
package gring
import (
"testing"
"testing"
)
var length = 10000
var r1 = New(length)
var r1 = New(length)
func BenchmarkRing_Put(b *testing.B) {
for i := 0; i < b.N; i++ {
r1.Put(i)
}
for i := 0; i < b.N; i++ {
r1.Put(i)
}
}
func BenchmarkRing_Next(b *testing.B) {
for i := 0; i < b.N; i++ {
r1.Next()
}
for i := 0; i < b.N; i++ {
r1.Next()
}
}
func BenchmarkRing_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
r1.Set(i)
}
for i := 0; i < b.N; i++ {
r1.Set(i)
}
}
func BenchmarkRing_Len(b *testing.B) {
for i := 0; i < b.N; i++ {
r1.Len()
}
for i := 0; i < b.N; i++ {
r1.Len()
}
}
func BenchmarkRing_Cap(b *testing.B) {
for i := 0; i < b.N; i++ {
r1.Cap()
}
}
for i := 0; i < b.N; i++ {
r1.Cap()
}
}

View File

@ -0,0 +1,229 @@
package gring_test
import (
"container/ring"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/container/gring"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
type Student struct {
position int
name string
upgrade bool
}
func TestRing_Val(t *testing.T) {
gtest.Case(t, func() {
//定义cap 为3的ring类型数据
r := gring.New(3, true)
//分别给3个元素初始化赋值
r.Put(&Student{1, "jimmy", true})
r.Put(&Student{2, "tom", true})
r.Put(&Student{3, "alon", false})
//元素取值并判断和预设值是否相等
gtest.Assert(r.Val().(*Student).name, "jimmy")
//从当前位置往后移两个元素
r.Move(2)
gtest.Assert(r.Val().(*Student).name, "alon")
//更新元素值
//测试 value == nil
r.Set(nil)
gtest.Assert(r.Val(), nil)
//测试value != nil
r.Set(&Student{3, "jack", true})
})
}
func TestRing_CapLen(t *testing.T) {
gtest.Case(t, func() {
r := gring.New(10)
r.Put("goframe")
//cap长度 10
gtest.Assert(r.Cap(), 10)
//已有数据项 1
gtest.Assert(r.Len(), 1)
})
}
func TestRing_Position(t *testing.T) {
gtest.Case(t, func() {
r := gring.New(2)
r.Put(1)
r.Put(2)
//往后移动1个元素
r.Next()
gtest.Assert(r.Val(), 2)
//往前移动1个元素
r.Prev()
gtest.Assert(r.Val(), 1)
})
}
func TestRing_Link(t *testing.T) {
gtest.Case(t, func() {
r := gring.New(3)
r.Put(1)
r.Put(2)
r.Put(3)
s := gring.New(2)
s.Put("a")
s.Put("b")
rs := r.Link(s)
gtest.Assert(rs.Move(2).Val(), "b")
})
}
func TestRing_Unlink(t *testing.T) {
gtest.Case(t, func() {
r := gring.New(5)
for i := 0; i < 5; i++ {
r.Put(i + 1)
}
// 1 2 3 4
// 删除当前位置往后的2个数据返回被删除的数据
// 重新计算s len
s := r.Unlink(2) // 2 3
gtest.Assert(s.Val(), 2)
gtest.Assert(s.Len(), 1)
})
}
func TestRing_Slice(t *testing.T) {
gtest.Case(t, func() {
ringLen := 5
r := gring.New(ringLen)
for i := 0; i < ringLen; i++ {
r.Put(i + 1)
}
r.Move(2) // 3
array := r.SliceNext() // [3 4 5 1 2]
gtest.Assert(array[0], 3)
gtest.Assert(len(array), 5)
//判断array是否等于[3 4 5 1 2]
ra := []int{3, 4, 5, 1, 2}
gtest.Assert(ra, array)
//第3个元素设为nil
r.Set(nil)
array2 := r.SliceNext() //[4 5 1 2]
//返回当前位置往后不为空的元素数组长度为4
gtest.Assert(array2, g.Slice{4, 5, 1, 2})
array3 := r.SlicePrev() //[2 1 5 4]
gtest.Assert(array3, g.Slice{2, 1, 5, 4})
s := gring.New(ringLen)
for i := 0; i < ringLen; i++ {
s.Put(i + 1)
}
array4 := s.SlicePrev() // []
gtest.Assert(array4, g.Slice{1, 5, 4, 3, 2})
})
}
func TestRing_RLockIterator(t *testing.T) {
gtest.Case(t, func() {
ringLen := 5
r := gring.New(ringLen)
//ring不存在有值元素
r.RLockIteratorNext(func(v interface{}) bool {
gtest.Assert(v, nil)
return false
})
r.RLockIteratorNext(func(v interface{}) bool {
gtest.Assert(v, nil)
return true
})
r.RLockIteratorPrev(func(v interface{}) bool {
gtest.Assert(v, nil)
return true
})
for i := 0; i < ringLen; i++ {
r.Put(i + 1)
}
//回调函数返回true,RLockIteratorNext遍历5次,期望值分别是1、2、3、4、5
i := 0
r.RLockIteratorNext(func(v interface{}) bool {
gtest.Assert(v, i+1)
i++
return true
})
//RLockIteratorPrev遍历1次返回 false,退出遍历
r.RLockIteratorPrev(func(v interface{}) bool {
gtest.Assert(v, 1)
return false
})
})
}
func TestRing_LockIterator(t *testing.T) {
gtest.Case(t, func() {
ringLen := 5
r := gring.New(ringLen)
//不存在有值元素
r.LockIteratorNext(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return false
})
r.LockIteratorNext(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return false
})
r.LockIteratorNext(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return true
})
r.LockIteratorPrev(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return false
})
r.LockIteratorPrev(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return true
})
//ring初始化元素值
for i := 0; i < ringLen; i++ {
r.Put(i + 1)
}
//往后遍历组成数据 [1,2,3,4,5]
array1 := g.Slice{1, 2, 3, 4, 5}
ii := 0
r.LockIteratorNext(func(item *ring.Ring) bool {
//校验每一次遍历取值是否是期望值
gtest.Assert(item.Value, array1[ii])
ii++
return true
})
//往后取3个元素组成数组
//获得 [1,5,4]
i := 0
a := g.Slice{1, 5, 4}
r.LockIteratorPrev(func(item *ring.Ring) bool {
if i > 2 {
return false
}
gtest.Assert(item.Value, a[i])
i++
return true
})
})
}

View File

@ -1,17 +1,325 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// Package gset provides kinds of concurrent-safe(alternative) sets.
//
// 并发安全集合.
// Package gset provides kinds of concurrent-safe/unsafe sets.
package gset
type Set = InterfaceSet
import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"strings"
)
// 默认Set类型
func New(unsafe...bool) *Set {
return NewInterfaceSet(unsafe...)
}
type Set struct {
mu *rwmutex.RWMutex
m map[interface{}]struct{}
}
// New create and returns a new set, which contains un-repeated items.
// The parameter <unsafe> used to specify whether using set in un-concurrent-safety,
// which is false in default.
func New(unsafe ...bool) *Set {
return NewSet(unsafe...)
}
// See New.
func NewSet(unsafe ...bool) *Set {
return &Set{
m: make(map[interface{}]struct{}),
mu: rwmutex.New(unsafe...),
}
}
// NewFrom returns a new set from <items>.
// Parameter <items> can be either a variable of any type, or a slice.
func NewFrom(items interface{}, unsafe ...bool) *Set {
m := make(map[interface{}]struct{})
for _, v := range gconv.Interfaces(items) {
m[v] = struct{}{}
}
return &Set{
m: m,
mu: rwmutex.New(unsafe...),
}
}
// Iterator iterates the set 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 {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
return set
}
// Add adds one or multiple items to the set.
func (set *Set) Add(item ...interface{}) *Set {
set.mu.Lock()
for _, v := range item {
set.m[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// Contains checks whether the set contains <item>.
func (set *Set) Contains(item interface{}) bool {
set.mu.RLock()
_, exists := set.m[item]
set.mu.RUnlock()
return exists
}
// Remove deletes <item> from set.
func (set *Set) Remove(item interface{}) *Set {
set.mu.Lock()
delete(set.m, item)
set.mu.Unlock()
return set
}
// Size returns the size of the set.
func (set *Set) Size() int {
set.mu.RLock()
l := len(set.m)
set.mu.RUnlock()
return l
}
// Clear deletes all items of the set.
func (set *Set) Clear() *Set {
set.mu.Lock()
set.m = 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.m))
for item := range set.m {
ret[i] = item
i++
}
set.mu.RUnlock()
return ret
}
// Join joins items with a string <glue>.
func (set *Set) Join(glue string) string {
return strings.Join(gconv.Strings(set.Slice()), ",")
}
// String returns items as a string, which are joined by char ','.
func (set *Set) String() string {
return set.Join(",")
}
// LockFunc locks writing with callback function <f>.
func (set *Set) LockFunc(f func(m map[interface{}]struct{})) {
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
}
// RLockFunc locks reading with callback function <f>.
func (set *Set) RLockFunc(f func(m map[interface{}]struct{})) {
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
}
// Equal checks whether the two sets equal.
func (set *Set) Equal(other *Set) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
if len(set.m) != len(other.m) {
return false
}
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// IsSubsetOf checks whether the current set is a sub-set of <other>.
func (set *Set) IsSubsetOf(other *Set) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// 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)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
newSet.m[k] = v
}
if set != other {
for k, v := range other.m {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// 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)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set == other {
continue
}
other.mu.RLock()
for k, v := range set.m {
if _, ok := other.m[k]; !ok {
newSet.m[k] = v
}
}
other.mu.RUnlock()
}
return
}
// 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)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
if _, ok := other.m[k]; ok {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Complement returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> are in <full> and not in <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)
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
full.mu.RLock()
defer full.mu.RUnlock()
}
for k, v := range full.m {
if _, ok := set.m[k]; !ok {
newSet.m[k] = v
}
}
return
}
// Merge adds items from <others> sets into <set>.
func (set *Set) Merge(others ...*Set) *Set {
set.mu.Lock()
defer set.mu.Unlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range other.m {
set.m[k] = v
}
if set != other {
other.mu.RUnlock()
}
}
return set
}
// Sum sums items.
// Note: The items should be converted to int type,
// or you'd get a result that you unexpected.
func (set *Set) Sum() (sum int) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
sum += gconv.Int(k)
}
return
}
// Pops randomly pops an item from set.
func (set *Set) Pop(size int) interface{} {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
return k
}
return nil
}
// Pops randomly pops <size> items from set.
func (set *Set) Pops(size int) []interface{} {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.m) {
size = len(set.m)
}
index := 0
array := make([]interface{}, size)
for k, _ := range set.m {
array[index] = k
index++
if index == size {
break
}
}
return array
}

View File

@ -1,16 +1,16 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
//
// You can obtain one at https://github.com/gogf/gf.
//
package gset
import (
"fmt"
"gitee.com/johng/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"strings"
)
type IntSet struct {
@ -18,43 +18,52 @@ type IntSet struct {
m map[int]struct{}
}
func NewIntSet(unsafe...bool) *IntSet {
// New create and returns a new set, which contains un-repeated items.
// The parameter <unsafe> used to specify whether using set in un-concurrent-safety,
// which is false in default.
func NewIntSet(unsafe ...bool) *IntSet {
return &IntSet{
m : make(map[int]struct{}),
mu : rwmutex.New(unsafe...),
m: make(map[int]struct{}),
mu: rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (set *IntSet) Iterator(f func (v int) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
// NewIntSetFrom returns a new set from <items>.
func NewIntSetFrom(items []int, unsafe ...bool) *IntSet {
m := make(map[int]struct{})
for _, v := range items {
m[v] = struct{}{}
}
return &IntSet{
m: m,
mu: rwmutex.New(unsafe...),
}
}
// Iterator iterates the set 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 {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
return set
}
// 设置键
func (set *IntSet) Add(item int) *IntSet {
// Add adds one or multiple items to the set.
func (set *IntSet) Add(item ...int) *IntSet {
set.mu.Lock()
set.m[item] = struct{}{}
for _, v := range item {
set.m[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// 批量添加设置键
func (set *IntSet) BatchAdd(items []int) *IntSet {
set.mu.Lock()
for _, item := range items {
set.m[item] = struct{}{}
}
set.mu.Unlock()
return set
}
// 键是否存在
// Contains checks whether the set contains <item>.
func (set *IntSet) Contains(item int) bool {
set.mu.RLock()
_, exists := set.m[item]
@ -62,14 +71,15 @@ func (set *IntSet) Contains(item int) bool {
return exists
}
// 删除键值对
func (set *IntSet) Remove(key int) {
// Remove deletes <item> from set.
func (set *IntSet) Remove(item int) *IntSet {
set.mu.Lock()
delete(set.m, key)
delete(set.m, item)
set.mu.Unlock()
return set
}
// 大小
// Size returns the size of the set.
func (set *IntSet) Size() int {
set.mu.RLock()
l := len(set.m)
@ -77,40 +87,233 @@ func (set *IntSet) Size() int {
return l
}
// 清空set
func (set *IntSet) Clear() {
// Clear deletes all items of the set.
func (set *IntSet) Clear() *IntSet {
set.mu.Lock()
set.m = 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.m))
i := 0
for item := range set.m {
ret[i] = item
for k, _ := range set.m {
ret[i] = k
i++
}
set.mu.RUnlock()
return ret
}
// 转换为字符串
func (set *IntSet) String() string {
return fmt.Sprint(set.Slice())
// Join joins items with a string <glue>.
func (set *IntSet) Join(glue string) string {
return strings.Join(gconv.Strings(set.Slice()), ",")
}
// String returns items as a string, which are joined by char ','.
func (set *IntSet) String() string {
return set.Join(",")
}
// LockFunc locks writing with callback function <f>.
func (set *IntSet) LockFunc(f func(m map[int]struct{})) {
set.mu.Lock(true)
defer set.mu.Unlock(true)
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
}
// RLockFunc locks reading with callback function <f>.
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
f(set.m)
}
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
}
// Equal checks whether the two sets equal.
func (set *IntSet) Equal(other *IntSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
if len(set.m) != len(other.m) {
return false
}
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// IsSubsetOf checks whether the current set is a sub-set of <other>.
func (set *IntSet) IsSubsetOf(other *IntSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// 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)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
newSet.m[k] = v
}
if set != other {
for k, v := range other.m {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// 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)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set == other {
continue
}
other.mu.RLock()
for k, v := range set.m {
if _, ok := other.m[k]; !ok {
newSet.m[k] = v
}
}
other.mu.RUnlock()
}
return
}
// 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)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
if _, ok := other.m[k]; ok {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Complement returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> are in <full> and not in <set>.
//
// 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)
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
full.mu.RLock()
defer full.mu.RUnlock()
}
for k, v := range full.m {
if _, ok := set.m[k]; !ok {
newSet.m[k] = v
}
}
return
}
// Merge adds items from <others> sets into <set>.
func (set *IntSet) Merge(others ...*IntSet) *IntSet {
set.mu.Lock()
defer set.mu.Unlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range other.m {
set.m[k] = v
}
if set != other {
other.mu.RUnlock()
}
}
return set
}
// Sum sums items.
// Note: The items should be converted to int type,
// or you'd get a result that you unexpected.
func (set *IntSet) Sum() (sum int) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
sum += k
}
return
}
// Pops randomly pops an item from set.
func (set *IntSet) Pop(size int) int {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
return k
}
return 0
}
// Pops randomly pops <size> items from set.
func (set *IntSet) Pops(size int) []int {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.m) {
size = len(set.m)
}
index := 0
array := make([]int, size)
for k, _ := range set.m {
array[index] = k
index++
if index == size {
break
}
}
return array
}

View File

@ -1,114 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
//
package gset
import (
"fmt"
"gitee.com/johng/gf/g/internal/rwmutex"
)
type InterfaceSet struct {
mu *rwmutex.RWMutex
m map[interface{}]struct{}
}
func NewInterfaceSet(unsafe...bool) *InterfaceSet {
return &InterfaceSet{
m : make(map[interface{}]struct{}),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (set *InterfaceSet) Iterator(f func (v interface{}) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
}
// 添加
func (set *InterfaceSet) Add(item interface{}) *InterfaceSet {
set.mu.Lock()
set.m[item] = struct{}{}
set.mu.Unlock()
return set
}
// 批量添加
func (set *InterfaceSet) BatchAdd(items []interface{}) *InterfaceSet {
set.mu.Lock()
for _, item := range items {
set.m[item] = struct{}{}
}
set.mu.Unlock()
return set
}
// 键是否存在
func (set *InterfaceSet) Contains(item interface{}) bool {
set.mu.RLock()
_, exists := set.m[item]
set.mu.RUnlock()
return exists
}
// 删除键值对
func (set *InterfaceSet) Remove(key interface{}) {
set.mu.Lock()
delete(set.m, key)
set.mu.Unlock()
}
// 大小
func (set *InterfaceSet) Size() int {
set.mu.RLock()
l := len(set.m)
set.mu.RUnlock()
return l
}
// 清空set
func (set *InterfaceSet) Clear() {
set.mu.Lock()
set.m = make(map[interface{}]struct{})
set.mu.Unlock()
}
// 转换为数组
func (set *InterfaceSet) Slice() []interface{} {
set.mu.RLock()
i := 0
ret := make([]interface{}, len(set.m))
for item := range set.m {
ret[i] = item
i++
}
set.mu.RUnlock()
return ret
}
// 转换为字符串
func (set *InterfaceSet) String() string {
return fmt.Sprint(set.Slice())
}
func (set *InterfaceSet) LockFunc(f func(m map[interface{}]struct{})) {
set.mu.Lock(true)
defer set.mu.Unlock(true)
f(set.m)
}
func (set *InterfaceSet) RLockFunc(f func(m map[interface{}]struct{})) {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
f(set.m)
}

View File

@ -1,15 +1,16 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// 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://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
//
package gset
import (
"fmt"
"gitee.com/johng/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"strings"
)
type StringSet struct {
@ -17,43 +18,52 @@ type StringSet struct {
m map[string]struct{}
}
func NewStringSet(unsafe...bool) *StringSet {
return &StringSet {
m : make(map[string]struct{}),
mu : rwmutex.New(unsafe...),
// New create and returns a new set, which contains un-repeated items.
// The parameter <unsafe> used to specify whether using set in un-concurrent-safety,
// which is false in default.
func NewStringSet(unsafe ...bool) *StringSet {
return &StringSet{
m: make(map[string]struct{}),
mu: rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (set *StringSet) Iterator(f func (v string) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
// NewStringSetFrom returns a new set from <items>.
func NewStringSetFrom(items []string, unsafe ...bool) *StringSet {
m := make(map[string]struct{})
for _, v := range items {
m[v] = struct{}{}
}
return &StringSet{
m: m,
mu: rwmutex.New(unsafe...),
}
}
// Iterator iterates the set with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *StringSet) Iterator(f func(v string) bool) *StringSet {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
return set
}
// 设置键
func (set *StringSet) Add(item string) *StringSet {
// Add adds one or multiple items to the set.
func (set *StringSet) Add(item ...string) *StringSet {
set.mu.Lock()
set.m[item] = struct{}{}
for _, v := range item {
set.m[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// 批量添加设置键
func (set *StringSet) BatchAdd(items []string) *StringSet {
set.mu.Lock()
for _, item := range items {
set.m[item] = struct{}{}
}
set.mu.Unlock()
return set
}
// 键是否存在
// Contains checks whether the set contains <item>.
func (set *StringSet) Contains(item string) bool {
set.mu.RLock()
_, exists := set.m[item]
@ -61,14 +71,15 @@ func (set *StringSet) Contains(item string) bool {
return exists
}
// 删除键值对
func (set *StringSet) Remove(key string) {
// Remove deletes <item> from set.
func (set *StringSet) Remove(item string) *StringSet {
set.mu.Lock()
delete(set.m, key)
delete(set.m, item)
set.mu.Unlock()
return set
}
// 大小
// Size returns the size of the set.
func (set *StringSet) Size() int {
set.mu.RLock()
l := len(set.m)
@ -76,14 +87,15 @@ func (set *StringSet) Size() int {
return l
}
// 清空set
func (set *StringSet) Clear() {
// Clear deletes all items of the set.
func (set *StringSet) Clear() *StringSet {
set.mu.Lock()
set.m = make(map[string]struct{})
set.mu.Unlock()
return set
}
// 转换为数组
// Slice returns the a of items of the set as slice.
func (set *StringSet) Slice() []string {
set.mu.RLock()
ret := make([]string, len(set.m))
@ -97,19 +109,212 @@ func (set *StringSet) Slice() []string {
return ret
}
// 转换为字符串
// Join joins items with a string <glue>.
func (set *StringSet) Join(glue string) string {
return strings.Join(set.Slice(), ",")
}
// String returns items as a string, which are joined by char ','.
func (set *StringSet) String() string {
return fmt.Sprint(set.Slice())
return set.Join(",")
}
// LockFunc locks writing with callback function <f>.
func (set *StringSet) LockFunc(f func(m map[string]struct{})) {
set.mu.Lock(true)
defer set.mu.Unlock(true)
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
}
// RLockFunc locks reading with callback function <f>.
func (set *StringSet) RLockFunc(f func(m map[string]struct{})) {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
}
// Equal checks whether the two sets equal.
func (set *StringSet) Equal(other *StringSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
if len(set.m) != len(other.m) {
return false
}
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// IsSubsetOf checks whether the current set is a sub-set of <other>.
func (set *StringSet) IsSubsetOf(other *StringSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// 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 *StringSet) Union(others ...*StringSet) (newSet *StringSet) {
newSet = NewStringSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
newSet.m[k] = v
}
if set != other {
for k, v := range other.m {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// 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 *StringSet) Diff(others ...*StringSet) (newSet *StringSet) {
newSet = NewStringSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set == other {
continue
}
other.mu.RLock()
for k, v := range set.m {
if _, ok := other.m[k]; !ok {
newSet.m[k] = v
}
}
other.mu.RUnlock()
}
return
}
// 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 *StringSet) Intersect(others ...*StringSet) (newSet *StringSet) {
newSet = NewStringSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
if _, ok := other.m[k]; ok {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Complement returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> are in <full> and not in <set>.
//
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *StringSet) Complement(full *StringSet) (newSet *StringSet) {
newSet = NewStringSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
full.mu.RLock()
defer full.mu.RUnlock()
}
for k, v := range full.m {
if _, ok := set.m[k]; !ok {
newSet.m[k] = v
}
}
return
}
// Merge adds items from <others> sets into <set>.
func (set *StringSet) Merge(others ...*StringSet) *StringSet {
set.mu.Lock()
defer set.mu.Unlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range other.m {
set.m[k] = v
}
if set != other {
other.mu.RUnlock()
}
}
return set
}
// Sum sums items.
// Note: The items should be converted to int type,
// or you'd get a result that you unexpected.
func (set *StringSet) Sum() (sum int) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
sum += gconv.Int(k)
}
return
}
// Pops randomly pops an item from set.
func (set *StringSet) Pop(size int) string {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
return k
}
return ""
}
// Pops randomly pops <size> items from set.
func (set *StringSet) Pops(size int) []string {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.m) {
size = len(set.m)
}
index := 0
array := make([]string, size)
for k, _ := range set.m {
array[index] = k
index++
if index == size {
break
}
}
return array
}

View File

@ -1,73 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*"
package gset_test
import (
"testing"
"strconv"
"gitee.com/johng/gf/g/container/gset"
)
var ints = gset.NewIntSet()
var itfs = gset.NewInterfaceSet()
var strs = gset.NewStringSet()
func Benchmark_IntSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Add(i)
}
}
func Benchmark_IntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Contains(i)
}
}
func Benchmark_IntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Remove(i)
}
}
func Benchmark_InterfaceSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Add(i)
}
}
func Benchmark_InterfaceSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Contains(i)
}
}
func Benchmark_InterfaceSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Remove(i)
}
}
func Benchmark_StringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Add(strconv.Itoa(i))
}
}
func Benchmark_StringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Contains(strconv.Itoa(i))
}
}
func Benchmark_StringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Remove(strconv.Itoa(i))
}
}

View File

@ -1,73 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*"
package gset_test
import (
"testing"
"strconv"
"gitee.com/johng/gf/g/container/gset"
)
var intsUnsafe = gset.NewIntSet(true)
var itfsUnsafe = gset.NewInterfaceSet(true)
var strsUnsafe = gset.NewStringSet(true)
func Benchmark_Unsafe_IntSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_IntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_IntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Remove(i)
}
}
func Benchmark_Unsafe_InterfaceSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_InterfaceSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_InterfaceSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Remove(i)
}
}
func Benchmark_Unsafe_StringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Add(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Contains(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Remove(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,130 @@
// 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.
// go test *.go -bench=".*"
package gset_test
import (
"github.com/gogf/gf/g/container/gset"
"strconv"
"testing"
)
var ints = gset.NewIntSet()
var itfs = gset.NewSet()
var strs = gset.NewStringSet()
var intsUnsafe = gset.NewIntSet(true)
var itfsUnsafe = gset.NewSet(true)
var strsUnsafe = gset.NewStringSet(true)
func Benchmark_IntSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Add(i)
}
}
func Benchmark_IntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Contains(i)
}
}
func Benchmark_IntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Remove(i)
}
}
func Benchmark_Set_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Add(i)
}
}
func Benchmark_Set_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Contains(i)
}
}
func Benchmark_Set_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Remove(i)
}
}
func Benchmark_StringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Add(strconv.Itoa(i))
}
}
func Benchmark_StringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Contains(strconv.Itoa(i))
}
}
func Benchmark_StringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Remove(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_IntSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_IntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_IntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Remove(i)
}
}
func Benchmark_Unsafe_Set_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_Set_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_Set_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Remove(i)
}
}
func Benchmark_Unsafe_StringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Add(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Contains(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Remove(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,216 @@
// 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.
// go test *.go
package gset_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/test/gtest"
"strings"
"testing"
)
func TestIntSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(1).Add(2)
s.Add([]int{3, 4}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestIntSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
a1 := garray.New()
a2 := garray.New()
s.Iterator(func(v int) bool {
a1.Append(1)
return false
})
s.Iterator(func(v int) bool {
a2.Append(1)
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestIntSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[int]struct{}) {
delete(m, 1)
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[int]struct{}) {
gtest.Assert(m, map[int]struct{}{
3: struct{}{},
2: struct{}{},
})
})
})
}
func TestIntSet_Equal(t *testing.T) {
gtest.Case(t, func() {
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)
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestIntSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
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)
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestIntSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s3 := s1.Union(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), true)
})
}
func TestIntSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), false)
gtest.Assert(s3.Contains(4), false)
})
}
func TestIntSet_Intersect(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Intersect(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), false)
})
}
func TestIntSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(4), true)
gtest.Assert(s3.Contains(5), true)
})
}
func TestIntSet_Size(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet(true)
s1.Add(1).Add(2).Add(3)
gtest.Assert(s1.Size(), 3)
})
}
func TestIntSet_Merge(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Merge(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(5), true)
gtest.Assert(s3.Contains(6), false)
})
}
func TestIntSet_Join(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s3 := s1.Join(",")
gtest.Assert(strings.Contains(s3, "3"), true)
})
}
func TestIntSet_Sum(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2 := gset.NewIntSet()
s2.Add(5).Add(6).Add(7)
gtest.Assert(s2.Sum(), 18)
})
}
func TestIntSet_Pop(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s1.Add(4).Add(2).Add(3)
gtest.AssertIN(s1.Pop(1), []int{4, 2, 3})
gtest.AssertIN(s1.Pop(5), []int{4, 2, 3})
gtest.Assert(s1.Size(), 3)
})
}

View File

@ -0,0 +1,256 @@
// 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.
// go test *.go
package gset_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/test/gtest"
"strings"
"testing"
)
func TestStringSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("1").Add("2")
s.Add([]string{"3", "4"}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN("1", s.Slice())
gtest.AssertIN("2", s.Slice())
gtest.AssertIN("3", s.Slice())
gtest.AssertIN("4", s.Slice())
gtest.AssertNI("0", s.Slice())
gtest.Assert(s.Contains("4"), true)
gtest.Assert(s.Contains("5"), false)
s.Remove("1")
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestStringSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("2").Add("3")
gtest.Assert(s.Size(), 3)
a1 := garray.New()
a2 := garray.New()
s.Iterator(func(v string) bool {
a1.Append("1")
return false
})
s.Iterator(func(v string) bool {
a2.Append("1")
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestStringSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("2").Add("3")
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[string]struct{}) {
delete(m, "1")
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[string]struct{}) {
gtest.Assert(m, map[string]struct{}{
"3": struct{}{},
"2": struct{}{},
})
})
})
}
func TestStringSet_Equal(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s3 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestStringSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s3 := gset.NewStringSet()
s1.Add("1").Add("2")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestStringSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2")
s2.Add("3").Add("4")
s3 := s1.Union(s2)
gtest.Assert(s3.Contains("1"), true)
gtest.Assert(s3.Contains("2"), true)
gtest.Assert(s3.Contains("3"), true)
gtest.Assert(s3.Contains("4"), true)
})
}
func TestStringSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains("1"), true)
gtest.Assert(s3.Contains("2"), true)
gtest.Assert(s3.Contains("3"), false)
gtest.Assert(s3.Contains("4"), false)
})
}
func TestStringSet_Intersect(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Intersect(s2)
gtest.Assert(s3.Contains("1"), false)
gtest.Assert(s3.Contains("2"), false)
gtest.Assert(s3.Contains("3"), true)
gtest.Assert(s3.Contains("4"), false)
})
}
func TestStringSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains("1"), false)
gtest.Assert(s3.Contains("2"), false)
gtest.Assert(s3.Contains("4"), true)
gtest.Assert(s3.Contains("5"), true)
})
}
func TestNewIntSetFrom(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSetFrom([]int{1, 2, 3, 4})
s2 := gset.NewIntSetFrom([]int{5, 6, 7, 8})
gtest.Assert(s1.Contains(3), true)
gtest.Assert(s1.Contains(5), false)
gtest.Assert(s2.Contains(3), false)
gtest.Assert(s2.Contains(5), true)
})
}
func TestStringSet_Merge(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Merge(s2)
gtest.Assert(s3.Contains("1"), true)
gtest.Assert(s3.Contains("6"), false)
gtest.Assert(s3.Contains("4"), true)
gtest.Assert(s3.Contains("5"), true)
})
}
func TestNewStringSetFrom(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
gtest.Assert(s1.Contains("b"), true)
gtest.Assert(s1.Contains("d"), false)
})
}
func TestStringSet_Join(t *testing.T) {
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
str1 := s1.Join(",")
gtest.Assert(strings.Contains(str1, "b"), true)
gtest.Assert(strings.Contains(str1, "d"), false)
}
func TestStringSet_String(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
str1 := s1.String()
gtest.Assert(strings.Contains(str1, "b"), true)
gtest.Assert(strings.Contains(str1, "d"), false)
})
}
func TestStringSet_Sum(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
s2 := gset.NewIntSetFrom([]int{2, 3, 4}, true)
gtest.Assert(s1.Sum(), 0)
gtest.Assert(s2.Sum(), 9)
})
}
func TestStringSet_Size(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
gtest.Assert(s1.Size(), 3)
})
}
func TestStringSet_Remove(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
s1 = s1.Remove("b")
gtest.Assert(s1.Contains("b"), false)
gtest.Assert(s1.Contains("c"), true)
})
}
func TestStringSet_Pop(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
str1 := s1.Pop(1)
gtest.Assert(strings.Contains("a,b,c", str1), true)
})
}
func TestStringSet_Pops(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSetFrom([]string{"a", "b", "c"}, true)
strs1 := s1.Pops(2)
gtest.AssertIN(strs1, []string{"a", "b", "c"})
gtest.Assert(len(strs1), 2)
str2 := s1.Pops(7)
gtest.AssertIN(str2, []string{"a", "b", "c"})
})
}

View File

@ -0,0 +1,268 @@
// 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.
// go test *.go
package gset_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/test/gtest"
"strings"
"testing"
)
func TestSet_New(t *testing.T) {
gtest.Case(t, func() {
s := gset.New()
s.Add(1).Add(1).Add(2)
s.Add([]interface{}{3, 4}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(1).Add(2)
s.Add([]interface{}{3, 4}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
a1 := garray.New()
a2 := garray.New()
s.Iterator(func(v interface{}) bool {
a1.Append(1)
return false
})
s.Iterator(func(v interface{}) bool {
a2.Append(1)
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[interface{}]struct{}) {
delete(m, 1)
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[interface{}]struct{}) {
gtest.Assert(m, map[interface{}]struct{}{
3: struct{}{},
2: struct{}{},
})
})
})
}
func TestSet_Equal(t *testing.T) {
gtest.Case(t, func() {
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)
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
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)
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s3 := s1.Union(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), true)
})
}
func TestSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), false)
gtest.Assert(s3.Contains(4), false)
})
}
func TestSet_Intersect(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Intersect(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), false)
})
}
func TestSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(4), true)
gtest.Assert(s3.Contains(5), true)
})
}
func TestNewFrom(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewFrom("a")
s2 := gset.NewFrom("b", false)
s3 := gset.NewFrom(3, true)
s4 := gset.NewFrom([]string{"s1", "s2"}, true)
gtest.Assert(s1.Contains("a"), true)
gtest.Assert(s2.Contains("b"), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s4.Contains("s1"), true)
gtest.Assert(s4.Contains("s3"), false)
})
}
func TestNew(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.New()
s1.Add("a").Add(2)
s2 := gset.New(true)
s2.Add("b").Add(3)
gtest.Assert(s1.Contains("a"), true)
})
}
func TestSet_Join(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.New(true)
s1.Add("a").Add("a1").Add("b").Add("c")
str1 := s1.Join(",")
gtest.Assert(strings.Contains(str1, "a1"), true)
})
}
func TestSet_String(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.New(true)
s1.Add("a").Add("a2").Add("b").Add("c")
str1 := s1.String()
gtest.Assert(strings.Contains(str1, "a2"), true)
})
}
func TestSet_Merge(t *testing.T) {
gtest.Case(t, func() {
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")
ss := s1.Merge(s2)
gtest.Assert(ss.Contains("a2"), true)
gtest.Assert(ss.Contains("b1"), true)
})
}
func TestSet_Sum(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.New(true)
s1.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.Sum(), int(10))
})
}
func TestSet_Pop(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.New(true)
s1.Add(1).Add(2).Add(3).Add(4)
gtest.AssertIN(s1.Pop(1), []int{1, 2, 3, 4})
})
}
func TestSet_Pops(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.New(true)
s1.Add(1).Add(2).Add(3).Add(4)
gtest.AssertIN(s1.Pops(1), []int{1, 2, 3, 4})
gtest.AssertIN(s1.Pops(6), []int{1, 2, 3, 4})
gtest.Assert(len(s1.Pops(2)), 2)
})
}

View File

@ -0,0 +1,8 @@
// 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 this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gtree provides concurrent-safe/unsafe tree containers.
package gtree

View File

@ -0,0 +1,706 @@
// 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 this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree
import (
"fmt"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
)
// AVLTree holds elements of the AVL tree.
type AVLTree struct {
mu *rwmutex.RWMutex
root *AVLTreeNode
comparator func(v1, v2 interface{}) int
size int
}
// AVLTreeNode is a single element within the tree.
type AVLTreeNode struct {
Key interface{}
Value interface{}
parent *AVLTreeNode
children [2]*AVLTreeNode
b int8
}
// NewAVLTree instantiates an AVL tree with the custom key comparator.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewAVLTree(comparator func(v1, v2 interface{}) int, unsafe ...bool) *AVLTree {
return &AVLTree{
mu: rwmutex.New(unsafe...),
comparator: comparator,
}
}
// NewAVLTreeFrom instantiates an AVL tree with the custom key comparator and data map.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewAVLTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe ...bool) *AVLTree {
tree := NewAVLTree(comparator, unsafe...)
for k, v := range data {
tree.put(k, v, nil, &tree.root)
}
return tree
}
// Clone returns a new tree with a copy of current tree.
func (tree *AVLTree) Clone(unsafe ...bool) *AVLTree {
newTree := NewAVLTree(tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set inserts node into the tree.
func (tree *AVLTree) Set(key interface{}, value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.put(key, value, nil, &tree.root)
}
// Sets batch sets key-values to the tree.
func (tree *AVLTree) Sets(data map[interface{}]interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for key, value := range data {
tree.put(key, value, nil, &tree.root)
}
}
// Search searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *AVLTree) Search(key interface{}) (value interface{}, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.doSearch(key)
}
// doSearch searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *AVLTree) doSearch(key interface{}) (value interface{}, found bool) {
n := tree.root
for n != nil {
cmp := tree.comparator(key, n.Key)
switch {
case cmp == 0:
return n.Value, true
case cmp < 0:
n = n.children[0]
case cmp > 0:
n = n.children[1]
}
}
return nil, false
}
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
func (tree *AVLTree) Get(key interface{}) (value interface{}) {
value, _ = tree.Search(key)
return
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (tree *AVLTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
tree.mu.Lock()
defer tree.mu.Unlock()
if v, ok := tree.doSearch(key); ok {
return v
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
tree.put(key, value, nil, &tree.root)
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (tree *AVLTree) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (tree *AVLTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (tree *AVLTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (tree *AVLTree) GetVar(key interface{}) *gvar.Var {
return gvar.New(tree.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (tree *AVLTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(tree.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (tree *AVLTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (tree *AVLTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *AVLTree) SetIfNotExist(key interface{}, value interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *AVLTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (tree *AVLTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f)
return true
}
return false
}
// Contains checks whether <key> exists in the tree.
func (tree *AVLTree) Contains(key interface{}) bool {
_, ok := tree.Search(key)
return ok
}
// Remove removes the node from the tree by key.
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (tree *AVLTree) Remove(key interface{}) (value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
value, _ = tree.remove(key, &tree.root)
return
}
// Removes batch deletes values of the tree by <keys>.
func (tree *AVLTree) Removes(keys []interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.remove(key, &tree.root)
}
}
// IsEmpty returns true if tree does not contain any nodes.
func (tree *AVLTree) IsEmpty() bool {
return tree.Size() == 0
}
// Size returns number of nodes in the tree.
func (tree *AVLTree) Size() int {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.size
}
// Keys returns all keys in asc order.
func (tree *AVLTree) Keys() []interface{} {
keys := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
keys[index] = key
index++
return true
})
return keys
}
// Values returns all values in asc order based on the key.
func (tree *AVLTree) Values() []interface{} {
values := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
values[index] = value
index++
return true
})
return values
}
// Left returns the minimum element of the AVL tree
// or nil if the tree is empty.
func (tree *AVLTree) Left() *AVLTreeNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.bottom(0)
if tree.mu.IsSafe() {
return &AVLTreeNode{
Key: node.Key,
Value: node.Value,
}
}
return node
}
// Right returns the maximum element of the AVL tree
// or nil if the tree is empty.
func (tree *AVLTree) Right() *AVLTreeNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.bottom(1)
if tree.mu.IsSafe() {
return &AVLTreeNode{
Key: node.Key,
Value: node.Value,
}
}
return node
}
// Floor Finds floor node of the input key, return the floor node or nil if no floor node is found.
// Second return parameter is true if floor was found, otherwise false.
//
// Floor node is defined as the largest node that is smaller than or equal to the given node.
// A floor node may not be found, either because the tree is empty, or because
// all nodes in the tree is larger than the given node.
//
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (tree *AVLTree) Floor(key interface{}) (floor *AVLTreeNode, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
c := tree.comparator(key, n.Key)
switch {
case c == 0:
return n, true
case c < 0:
n = n.children[0]
case c > 0:
floor, found = n, true
n = n.children[1]
}
}
if found {
return
}
return nil, false
}
// Ceiling finds ceiling node of the input key, return the ceiling node or nil if no ceiling node is found.
// Second return parameter is true if ceiling was found, otherwise false.
//
// Ceiling node is defined as the smallest node that is larger than or equal to the given node.
// A ceiling node may not be found, either because the tree is empty, or because
// all nodes in the tree is smaller than the given node.
//
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (tree *AVLTree) Ceiling(key interface{}) (ceiling *AVLTreeNode, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
c := tree.comparator(key, n.Key)
switch {
case c == 0:
return n, true
case c > 0:
n = n.children[1]
case c < 0:
ceiling, found = n, true
n = n.children[0]
}
}
if found {
return
}
return nil, false
}
// Clear removes all nodes from the tree.
func (tree *AVLTree) Clear() {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.root = nil
tree.size = 0
}
// String returns a string representation of container
func (tree *AVLTree) String() string {
tree.mu.RLock()
defer tree.mu.RUnlock()
str := "AVLTree\n"
if tree.size != 0 {
output(tree.root, "", true, &str)
}
return str
}
// Print prints the tree to stdout.
func (tree *AVLTree) Print() {
fmt.Println(tree.String())
}
// Map returns all key-value items as map.
func (tree *AVLTree) Map() map[interface{}]interface{} {
m := make(map[interface{}]interface{}, tree.Size())
tree.IteratorAsc(func(key, value interface{}) bool {
m[key] = value
return true
})
return m
}
// Flip exchanges key-value of the tree to value-key.
// Note that you should guarantee the value is the same type as key,
// or else the comparator would panic.
//
// If the type of value is different with key, you pass the new <comparator>.
func (tree *AVLTree) Flip(comparator ...func(v1, v2 interface{}) int) {
t := (*AVLTree)(nil)
if len(comparator) > 0 {
t = NewAVLTree(comparator[0], !tree.mu.IsSafe())
} else {
t = NewAVLTree(tree.comparator, !tree.mu.IsSafe())
}
tree.IteratorAsc(func(key, value interface{}) bool {
t.put(value, key, nil, &t.root)
return true
})
tree.mu.Lock()
tree.root = t.root
tree.size = t.size
tree.mu.Unlock()
}
// Iterator is alias of IteratorAsc.
func (tree *AVLTree) Iterator(f func(key, value interface{}) bool) {
tree.IteratorAsc(f)
}
// IteratorAsc iterates the tree 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()
defer tree.mu.RUnlock()
node := tree.bottom(0)
for node != nil {
if !f(node.Key, node.Value) {
return
}
node = node.Next()
}
}
// IteratorDesc iterates the tree 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()
defer tree.mu.RUnlock()
node := tree.bottom(1)
for node != nil {
if !f(node.Key, node.Value) {
return
}
node = node.Prev()
}
}
func (tree *AVLTree) put(key interface{}, value interface{}, p *AVLTreeNode, qp **AVLTreeNode) bool {
q := *qp
if q == nil {
tree.size++
*qp = &AVLTreeNode{Key: key, Value: value, parent: p}
return true
}
c := tree.comparator(key, q.Key)
if c == 0 {
q.Key = key
q.Value = value
return false
}
if c < 0 {
c = -1
} else {
c = 1
}
a := (c + 1) / 2
if tree.put(key, value, q, &q.children[a]) {
return putFix(int8(c), qp)
}
return false
}
func (tree *AVLTree) remove(key interface{}, qp **AVLTreeNode) (value interface{}, fix bool) {
q := *qp
if q == nil {
return nil, false
}
c := tree.comparator(key, q.Key)
if c == 0 {
tree.size--
value = q.Value
fix = true
if q.children[1] == nil {
if q.children[0] != nil {
q.children[0].parent = q.parent
}
*qp = q.children[0]
return
}
if removeMin(&q.children[1], &q.Key, &q.Value) {
return value, removeFix(-1, qp)
}
return
}
if c < 0 {
c = -1
} else {
c = 1
}
a := (c + 1) / 2
value, fix = tree.remove(key, &q.children[a])
if fix {
return value, removeFix(int8(-c), qp)
}
return value, false
}
func removeMin(qp **AVLTreeNode, minKey *interface{}, minVal *interface{}) bool {
q := *qp
if q.children[0] == nil {
*minKey = q.Key
*minVal = q.Value
if q.children[1] != nil {
q.children[1].parent = q.parent
}
*qp = q.children[1]
return true
}
fix := removeMin(&q.children[0], minKey, minVal)
if fix {
return removeFix(1, qp)
}
return false
}
func putFix(c int8, t **AVLTreeNode) bool {
s := *t
if s.b == 0 {
s.b = c
return true
}
if s.b == -c {
s.b = 0
return false
}
if s.children[(c+1)/2].b == c {
s = singleRotate(c, s)
} else {
s = doubleRotate(c, s)
}
*t = s
return false
}
func removeFix(c int8, t **AVLTreeNode) bool {
s := *t
if s.b == 0 {
s.b = c
return false
}
if s.b == -c {
s.b = 0
return true
}
a := (c + 1) / 2
if s.children[a].b == 0 {
s = rotate(c, s)
s.b = -c
*t = s
return false
}
if s.children[a].b == c {
s = singleRotate(c, s)
} else {
s = doubleRotate(c, s)
}
*t = s
return true
}
func singleRotate(c int8, s *AVLTreeNode) *AVLTreeNode {
s.b = 0
s = rotate(c, s)
s.b = 0
return s
}
func doubleRotate(c int8, s *AVLTreeNode) *AVLTreeNode {
a := (c + 1) / 2
r := s.children[a]
s.children[a] = rotate(-c, s.children[a])
p := rotate(c, s)
switch {
default:
s.b = 0
r.b = 0
case p.b == c:
s.b = -c
r.b = 0
case p.b == -c:
s.b = 0
r.b = c
}
p.b = 0
return p
}
func rotate(c int8, s *AVLTreeNode) *AVLTreeNode {
a := (c + 1) / 2
r := s.children[a]
s.children[a] = r.children[a^1]
if s.children[a] != nil {
s.children[a].parent = s
}
r.children[a^1] = s
r.parent = s.parent
s.parent = r
return r
}
func (tree *AVLTree) bottom(d int) *AVLTreeNode {
n := tree.root
if n == nil {
return nil
}
for c := n.children[d]; c != nil; c = n.children[d] {
n = c
}
return n
}
// Prev returns the previous element in an inorder
// walk of the AVL tree.
func (node *AVLTreeNode) Prev() *AVLTreeNode {
return node.walk1(0)
}
// Next returns the next element in an inorder
// walk of the AVL tree.
func (node *AVLTreeNode) Next() *AVLTreeNode {
return node.walk1(1)
}
func (node *AVLTreeNode) walk1(a int) *AVLTreeNode {
if node == nil {
return nil
}
n := node
if n.children[a] != nil {
n = n.children[a]
for n.children[a^1] != nil {
n = n.children[a^1]
}
return n
}
p := n.parent
for p != nil && p.children[a] == n {
n = p
p = p.parent
}
return p
}
func output(node *AVLTreeNode, prefix string, isTail bool, str *string) {
if node.children[1] != nil {
newPrefix := prefix
if isTail {
newPrefix += "│ "
} else {
newPrefix += " "
}
output(node.children[1], newPrefix, false, str)
}
*str += prefix
if isTail {
*str += "└── "
} else {
*str += "┌── "
}
*str += fmt.Sprintf("%v\n", node.Key)
if node.children[0] != nil {
newPrefix := prefix
if isTail {
newPrefix += " "
} else {
newPrefix += "│ "
}
output(node.children[0], newPrefix, true, str)
}
}

View File

@ -0,0 +1,847 @@
// 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 this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree
import (
"bytes"
"fmt"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
"strings"
)
// BTree holds elements of the B-tree.
type BTree struct {
mu *rwmutex.RWMutex
root *BTreeNode
comparator func(v1, v2 interface{}) int
size int // Total number of keys in the tree
m int // order (maximum number of children)
}
// BTreeNode is a single element within the tree.
type BTreeNode struct {
Parent *BTreeNode
Entries []*BTreeEntry // Contained keys in node
Children []*BTreeNode // Children nodes
}
// BTreeEntry represents the key-value pair contained within nodes.
type BTreeEntry struct {
Key interface{}
Value interface{}
}
// NewBTree instantiates a B-tree with <m> (maximum number of children) and a custom key comparator.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
// Note that the <m> must be greater or equal than 3, or else it panics.
func NewBTree(m int, comparator func(v1, v2 interface{}) int, unsafe ...bool) *BTree {
if m < 3 {
panic("Invalid order, should be at least 3")
}
return &BTree{
comparator: comparator,
mu: rwmutex.New(unsafe...),
m: m,
}
}
// NewBTreeFrom instantiates a B-tree with <m> (maximum number of children), a custom key comparator and data map.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe ...bool) *BTree {
tree := NewBTree(m, comparator, unsafe...)
for k, v := range data {
tree.doSet(k, v)
}
return tree
}
// Clone returns a new tree with a copy of current tree.
func (tree *BTree) Clone(unsafe ...bool) *BTree {
newTree := NewBTree(tree.m, tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set inserts key-value item into the tree.
func (tree *BTree) Set(key interface{}, value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.doSet(key, value)
}
// doSet inserts key-value pair node into the tree.
// If key already exists, then its value is updated with the new value.
func (tree *BTree) doSet(key interface{}, value interface{}) {
entry := &BTreeEntry{Key: key, Value: value}
if tree.root == nil {
tree.root = &BTreeNode{Entries: []*BTreeEntry{entry}, Children: []*BTreeNode{}}
tree.size++
return
}
if tree.insert(tree.root, entry) {
tree.size++
}
}
// Sets batch sets key-values to the tree.
func (tree *BTree) Sets(data map[interface{}]interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for k, v := range data {
tree.doSet(k, v)
}
}
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
func (tree *BTree) Get(key interface{}) (value interface{}) {
value, _ = tree.Search(key)
return
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (tree *BTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
tree.mu.Lock()
defer tree.mu.Unlock()
if entry := tree.doSearch(key); entry != nil {
return entry.Value
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
tree.doSet(key, value)
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (tree *BTree) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (tree *BTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (tree *BTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (tree *BTree) GetVar(key interface{}) *gvar.Var {
return gvar.New(tree.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (tree *BTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(tree.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (tree *BTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (tree *BTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *BTree) SetIfNotExist(key interface{}, value interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *BTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (tree *BTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f)
return true
}
return false
}
// Contains checks whether <key> exists in the tree.
func (tree *BTree) Contains(key interface{}) bool {
_, ok := tree.Search(key)
return ok
}
// Remove remove the node from the tree by key.
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (tree *BTree) doRemove(key interface{}) (value interface{}) {
node, index, found := tree.searchRecursively(tree.root, key)
if found {
value = node.Entries[index].Value
tree.delete(node, index)
tree.size--
}
return
}
// Remove removes the node from the tree by <key>.
func (tree *BTree) Remove(key interface{}) (value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
return tree.doRemove(key)
}
// Removes batch deletes values of the tree by <keys>.
func (tree *BTree) Removes(keys []interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.doRemove(key)
}
}
// Empty returns true if tree does not contain any nodes
func (tree *BTree) IsEmpty() bool {
return tree.Size() == 0
}
// Size returns number of nodes in the tree.
func (tree *BTree) Size() int {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.size
}
// Keys returns all keys in asc order.
func (tree *BTree) Keys() []interface{} {
keys := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
keys[index] = key
index++
return true
})
return keys
}
// Values returns all values in asc order based on the key.
func (tree *BTree) Values() []interface{} {
values := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
values[index] = value
index++
return true
})
return values
}
// Map returns all key-value items as map.
func (tree *BTree) Map() map[interface{}]interface{} {
m := make(map[interface{}]interface{}, tree.Size())
tree.IteratorAsc(func(key, value interface{}) bool {
m[key] = value
return true
})
return m
}
// Clear removes all nodes from the tree.
func (tree *BTree) Clear() {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.root = nil
tree.size = 0
}
// Height returns the height of the tree.
func (tree *BTree) Height() int {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.root.height()
}
// Left returns the left-most (min) entry or nil if tree is empty.
func (tree *BTree) Left() *BTreeEntry {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.left(tree.root)
return node.Entries[0]
}
// Right returns the right-most (max) entry or nil if tree is empty.
func (tree *BTree) Right() *BTreeEntry {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.right(tree.root)
return node.Entries[len(node.Entries)-1]
}
// String returns a string representation of container (for debugging purposes)
func (tree *BTree) String() string {
tree.mu.RLock()
defer tree.mu.RUnlock()
var buffer bytes.Buffer
if _, err := buffer.WriteString("BTree\n"); err != nil {
}
if tree.size != 0 {
tree.output(&buffer, tree.root, 0, true)
}
return buffer.String()
}
// Search searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *BTree) Search(key interface{}) (value interface{}, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node, index, found := tree.searchRecursively(tree.root, key)
if found {
return node.Entries[index].Value, true
}
return nil, false
}
// Search searches the tree with given <key> without mutex.
// It returns the entry if found or otherwise nil.
func (tree *BTree) doSearch(key interface{}) *BTreeEntry {
node, index, found := tree.searchRecursively(tree.root, key)
if found {
return node.Entries[index]
}
return nil
}
// Print prints the tree to stdout.
func (tree *BTree) Print() {
fmt.Println(tree.String())
}
// Iterator is alias of IteratorAsc.
func (tree *BTree) Iterator(f func(key, value interface{}) bool) {
tree.IteratorAsc(f)
}
// IteratorAsc iterates the tree 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()
defer tree.mu.RUnlock()
node := tree.left(tree.root)
if node == nil {
return
}
entry := node.Entries[0]
loop:
if entry == nil {
return
}
if !f(entry.Key, entry.Value) {
return
}
// Find current entry position in current node
e, _ := tree.search(node, entry.Key)
// Try to go down to the child right of the current entry
if e+1 < len(node.Children) {
node = node.Children[e+1]
// Try to go down to the child left of the current node
for len(node.Children) > 0 {
node = node.Children[0]
}
// Return the left-most entry
entry = node.Entries[0]
goto loop
}
// Above assures that we have reached a leaf node, so return the next entry in current node (if any)
if e+1 < len(node.Entries) {
entry = node.Entries[e+1]
goto loop
}
// Reached leaf node and there are no entries to the right of the current entry, so go up to the parent
for node.Parent != nil {
node = node.Parent
// Find next entry position in current node (note: search returns the first equal or bigger than entry)
e, _ := tree.search(node, entry.Key)
// Check that there is a next entry position in current node
if e < len(node.Entries) {
entry = node.Entries[e]
goto loop
}
}
}
// IteratorDesc iterates the tree 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()
defer tree.mu.RUnlock()
node := tree.right(tree.root)
if node == nil {
return
}
entry := node.Entries[len(node.Entries)-1]
loop:
if entry == nil {
return
}
if !f(entry.Key, entry.Value) {
return
}
// Find current entry position in current node
e, _ := tree.search(node, entry.Key)
// Try to go down to the child left of the current entry
if e < len(node.Children) {
node = node.Children[e]
// Try to go down to the child right of the current node
for len(node.Children) > 0 {
node = node.Children[len(node.Children)-1]
}
// Return the right-most entry
entry = node.Entries[len(node.Entries)-1]
goto loop
}
// Above assures that we have reached a leaf node, so return the previous entry in current node (if any)
if e-1 >= 0 {
entry = node.Entries[e-1]
goto loop
}
// Reached leaf node and there are no entries to the left of the current entry, so go up to the parent
for node.Parent != nil {
node = node.Parent
// Find previous entry position in current node (note: search returns the first equal or bigger than entry)
e, _ := tree.search(node, entry.Key)
// Check that there is a previous entry position in current node
if e-1 >= 0 {
entry = node.Entries[e-1]
goto loop
}
}
}
func (tree *BTree) output(buffer *bytes.Buffer, node *BTreeNode, level int, isTail bool) {
for e := 0; e < len(node.Entries)+1; e++ {
if e < len(node.Children) {
tree.output(buffer, node.Children[e], level+1, true)
}
if e < len(node.Entries) {
if _, err := buffer.WriteString(strings.Repeat(" ", level)); err != nil {
}
if _, err := buffer.WriteString(fmt.Sprintf("%v", node.Entries[e].Key) + "\n"); err != nil {
}
}
}
}
func (node *BTreeNode) height() int {
h := 0
n := node
for ; n != nil; n = n.Children[0] {
h++
if len(n.Children) == 0 {
break
}
}
return h
}
func (tree *BTree) isLeaf(node *BTreeNode) bool {
return len(node.Children) == 0
}
//func (tree *BTree) isFull(node *BTreeNode) bool {
// return len(node.Entries) == tree.maxEntries()
//}
func (tree *BTree) shouldSplit(node *BTreeNode) bool {
return len(node.Entries) > tree.maxEntries()
}
func (tree *BTree) maxChildren() int {
return tree.m
}
func (tree *BTree) minChildren() int {
return (tree.m + 1) / 2 // ceil(m/2)
}
func (tree *BTree) maxEntries() int {
return tree.maxChildren() - 1
}
func (tree *BTree) minEntries() int {
return tree.minChildren() - 1
}
func (tree *BTree) middle() int {
// "-1" to favor right nodes to have more keys when splitting
return (tree.m - 1) / 2
}
// search searches only within the single node among its entries
func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bool) {
low, mid, high := 0, 0, len(node.Entries)-1
for low <= high {
mid = (high + low) / 2
compare := tree.comparator(key, node.Entries[mid].Key)
switch {
case compare > 0:
low = mid + 1
case compare < 0:
high = mid - 1
case compare == 0:
return mid, true
}
}
return low, false
}
// searchRecursively searches recursively down the tree starting at the startNode
func (tree *BTree) searchRecursively(startNode *BTreeNode, key interface{}) (node *BTreeNode, index int, found bool) {
if tree.size == 0 {
return nil, -1, false
}
node = startNode
for {
index, found = tree.search(node, key)
if found {
return node, index, true
}
if tree.isLeaf(node) {
return nil, -1, false
}
node = node.Children[index]
}
}
func (tree *BTree) insert(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
if tree.isLeaf(node) {
return tree.insertIntoLeaf(node, entry)
}
return tree.insertIntoInternal(node, entry)
}
func (tree *BTree) insertIntoLeaf(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
insertPosition, found := tree.search(node, entry.Key)
if found {
node.Entries[insertPosition] = entry
return false
}
// Insert entry's key in the middle of the node
node.Entries = append(node.Entries, nil)
copy(node.Entries[insertPosition+1:], node.Entries[insertPosition:])
node.Entries[insertPosition] = entry
tree.split(node)
return true
}
func (tree *BTree) insertIntoInternal(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
insertPosition, found := tree.search(node, entry.Key)
if found {
node.Entries[insertPosition] = entry
return false
}
return tree.insert(node.Children[insertPosition], entry)
}
func (tree *BTree) split(node *BTreeNode) {
if !tree.shouldSplit(node) {
return
}
if node == tree.root {
tree.splitRoot()
return
}
tree.splitNonRoot(node)
}
func (tree *BTree) splitNonRoot(node *BTreeNode) {
middle := tree.middle()
parent := node.Parent
left := &BTreeNode{Entries: append([]*BTreeEntry(nil), node.Entries[:middle]...), Parent: parent}
right := &BTreeNode{Entries: append([]*BTreeEntry(nil), node.Entries[middle+1:]...), Parent: parent}
// Move children from the node to be split into left and right nodes
if !tree.isLeaf(node) {
left.Children = append([]*BTreeNode(nil), node.Children[:middle+1]...)
right.Children = append([]*BTreeNode(nil), node.Children[middle+1:]...)
setParent(left.Children, left)
setParent(right.Children, right)
}
insertPosition, _ := tree.search(parent, node.Entries[middle].Key)
// Insert middle key into parent
parent.Entries = append(parent.Entries, nil)
copy(parent.Entries[insertPosition+1:], parent.Entries[insertPosition:])
parent.Entries[insertPosition] = node.Entries[middle]
// Set child left of inserted key in parent to the created left node
parent.Children[insertPosition] = left
// Set child right of inserted key in parent to the created right node
parent.Children = append(parent.Children, nil)
copy(parent.Children[insertPosition+2:], parent.Children[insertPosition+1:])
parent.Children[insertPosition+1] = right
tree.split(parent)
}
func (tree *BTree) splitRoot() {
middle := tree.middle()
left := &BTreeNode{Entries: append([]*BTreeEntry(nil), tree.root.Entries[:middle]...)}
right := &BTreeNode{Entries: append([]*BTreeEntry(nil), tree.root.Entries[middle+1:]...)}
// Move children from the node to be split into left and right nodes
if !tree.isLeaf(tree.root) {
left.Children = append([]*BTreeNode(nil), tree.root.Children[:middle+1]...)
right.Children = append([]*BTreeNode(nil), tree.root.Children[middle+1:]...)
setParent(left.Children, left)
setParent(right.Children, right)
}
// Root is a node with one entry and two children (left and right)
newRoot := &BTreeNode{
Entries: []*BTreeEntry{tree.root.Entries[middle]},
Children: []*BTreeNode{left, right},
}
left.Parent = newRoot
right.Parent = newRoot
tree.root = newRoot
}
func setParent(nodes []*BTreeNode, parent *BTreeNode) {
for _, node := range nodes {
node.Parent = parent
}
}
func (tree *BTree) left(node *BTreeNode) *BTreeNode {
if tree.size == 0 {
return nil
}
current := node
for {
if tree.isLeaf(current) {
return current
}
current = current.Children[0]
}
}
func (tree *BTree) right(node *BTreeNode) *BTreeNode {
if tree.size == 0 {
return nil
}
current := node
for {
if tree.isLeaf(current) {
return current
}
current = current.Children[len(current.Children)-1]
}
}
// leftSibling returns the node's left sibling and child index (in parent) if it exists, otherwise (nil,-1)
// key is any of keys in node (could even be deleted).
func (tree *BTree) leftSibling(node *BTreeNode, key interface{}) (*BTreeNode, int) {
if node.Parent != nil {
index, _ := tree.search(node.Parent, key)
index--
if index >= 0 && index < len(node.Parent.Children) {
return node.Parent.Children[index], index
}
}
return nil, -1
}
// rightSibling returns the node's right sibling and child index (in parent) if it exists, otherwise (nil,-1)
// key is any of keys in node (could even be deleted).
func (tree *BTree) rightSibling(node *BTreeNode, key interface{}) (*BTreeNode, int) {
if node.Parent != nil {
index, _ := tree.search(node.Parent, key)
index++
if index < len(node.Parent.Children) {
return node.Parent.Children[index], index
}
}
return nil, -1
}
// delete deletes an entry in node at entries' index
// ref.: https://en.wikipedia.org/wiki/B-tree#Deletion
func (tree *BTree) delete(node *BTreeNode, index int) {
// deleting from a leaf node
if tree.isLeaf(node) {
deletedKey := node.Entries[index].Key
tree.deleteEntry(node, index)
tree.rebalance(node, deletedKey)
if len(tree.root.Entries) == 0 {
tree.root = nil
}
return
}
// deleting from an internal node
leftLargestNode := tree.right(node.Children[index]) // largest node in the left sub-tree (assumed to exist)
leftLargestEntryIndex := len(leftLargestNode.Entries) - 1
node.Entries[index] = leftLargestNode.Entries[leftLargestEntryIndex]
deletedKey := leftLargestNode.Entries[leftLargestEntryIndex].Key
tree.deleteEntry(leftLargestNode, leftLargestEntryIndex)
tree.rebalance(leftLargestNode, deletedKey)
}
// rebalance rebalances the tree after deletion if necessary and returns true, otherwise false.
// Note that we first delete the entry and then call rebalance, thus the passed deleted key as reference.
func (tree *BTree) rebalance(node *BTreeNode, deletedKey interface{}) {
// check if rebalancing is needed
if node == nil || len(node.Entries) >= tree.minEntries() {
return
}
// try to borrow from left sibling
leftSibling, leftSiblingIndex := tree.leftSibling(node, deletedKey)
if leftSibling != nil && len(leftSibling.Entries) > tree.minEntries() {
// rotate right
node.Entries = append([]*BTreeEntry{node.Parent.Entries[leftSiblingIndex]}, node.Entries...) // prepend parent's separator entry to node's entries
node.Parent.Entries[leftSiblingIndex] = leftSibling.Entries[len(leftSibling.Entries)-1]
tree.deleteEntry(leftSibling, len(leftSibling.Entries)-1)
if !tree.isLeaf(leftSibling) {
leftSiblingRightMostChild := leftSibling.Children[len(leftSibling.Children)-1]
leftSiblingRightMostChild.Parent = node
node.Children = append([]*BTreeNode{leftSiblingRightMostChild}, node.Children...)
tree.deleteChild(leftSibling, len(leftSibling.Children)-1)
}
return
}
// try to borrow from right sibling
rightSibling, rightSiblingIndex := tree.rightSibling(node, deletedKey)
if rightSibling != nil && len(rightSibling.Entries) > tree.minEntries() {
// rotate left
node.Entries = append(node.Entries, node.Parent.Entries[rightSiblingIndex-1]) // append parent's separator entry to node's entries
node.Parent.Entries[rightSiblingIndex-1] = rightSibling.Entries[0]
tree.deleteEntry(rightSibling, 0)
if !tree.isLeaf(rightSibling) {
rightSiblingLeftMostChild := rightSibling.Children[0]
rightSiblingLeftMostChild.Parent = node
node.Children = append(node.Children, rightSiblingLeftMostChild)
tree.deleteChild(rightSibling, 0)
}
return
}
// merge with siblings
if rightSibling != nil {
// merge with right sibling
node.Entries = append(node.Entries, node.Parent.Entries[rightSiblingIndex-1])
node.Entries = append(node.Entries, rightSibling.Entries...)
deletedKey = node.Parent.Entries[rightSiblingIndex-1].Key
tree.deleteEntry(node.Parent, rightSiblingIndex-1)
tree.appendChildren(node.Parent.Children[rightSiblingIndex], node)
tree.deleteChild(node.Parent, rightSiblingIndex)
} else if leftSibling != nil {
// merge with left sibling
entries := append([]*BTreeEntry(nil), leftSibling.Entries...)
entries = append(entries, node.Parent.Entries[leftSiblingIndex])
node.Entries = append(entries, node.Entries...)
deletedKey = node.Parent.Entries[leftSiblingIndex].Key
tree.deleteEntry(node.Parent, leftSiblingIndex)
tree.prependChildren(node.Parent.Children[leftSiblingIndex], node)
tree.deleteChild(node.Parent, leftSiblingIndex)
}
// make the merged node the root if its parent was the root and the root is empty
if node.Parent == tree.root && len(tree.root.Entries) == 0 {
tree.root = node
node.Parent = nil
return
}
// parent might underflow, so try to rebalance if necessary
tree.rebalance(node.Parent, deletedKey)
}
func (tree *BTree) prependChildren(fromNode *BTreeNode, toNode *BTreeNode) {
children := append([]*BTreeNode(nil), fromNode.Children...)
toNode.Children = append(children, toNode.Children...)
setParent(fromNode.Children, toNode)
}
func (tree *BTree) appendChildren(fromNode *BTreeNode, toNode *BTreeNode) {
toNode.Children = append(toNode.Children, fromNode.Children...)
setParent(fromNode.Children, toNode)
}
func (tree *BTree) deleteEntry(node *BTreeNode, index int) {
copy(node.Entries[index:], node.Entries[index+1:])
node.Entries[len(node.Entries)-1] = nil
node.Entries = node.Entries[:len(node.Entries)-1]
}
func (tree *BTree) deleteChild(node *BTreeNode, index int) {
if index >= len(node.Children) {
return
}
copy(node.Children[index:], node.Children[index+1:])
node.Children[len(node.Children)-1] = nil
node.Children = node.Children[:len(node.Children)-1]
}

View File

@ -0,0 +1,835 @@
// 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 this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree
import (
"fmt"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
)
type color bool
const (
black, red color = true, false
)
// RedBlackTree holds elements of the red-black tree.
type RedBlackTree struct {
mu *rwmutex.RWMutex
root *RedBlackTreeNode
size int
comparator func(v1, v2 interface{}) int
}
// RedBlackTreeNode is a single element within the tree.
type RedBlackTreeNode struct {
Key interface{}
Value interface{}
color color
left *RedBlackTreeNode
right *RedBlackTreeNode
parent *RedBlackTreeNode
}
// NewRedBlackTree instantiates a red-black tree with the custom key comparator.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewRedBlackTree(comparator func(v1, v2 interface{}) int, unsafe ...bool) *RedBlackTree {
return &RedBlackTree{
mu: rwmutex.New(unsafe...),
comparator: comparator,
}
}
// NewRedBlackTreeFrom instantiates a red-black tree with the custom key comparator and <data> map.
// The parameter <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewRedBlackTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe ...bool) *RedBlackTree {
tree := NewRedBlackTree(comparator, unsafe...)
for k, v := range data {
tree.doSet(k, v)
}
return tree
}
// Clone returns a new tree with a copy of current tree.
func (tree *RedBlackTree) Clone(unsafe ...bool) *RedBlackTree {
newTree := NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set inserts key-value item into the tree.
func (tree *RedBlackTree) Set(key interface{}, value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.doSet(key, value)
}
// Sets batch sets key-values to the tree.
func (tree *RedBlackTree) Sets(data map[interface{}]interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for k, v := range data {
tree.doSet(k, v)
}
}
// doSet inserts key-value item into the tree without mutex.
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.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)
switch {
case compare == 0:
//node.Key = key
node.Value = value
return
case compare < 0:
if node.left == nil {
node.left = &RedBlackTreeNode{Key: key, Value: value, color: red}
insertedNode = node.left
loop = false
} else {
node = node.left
}
case compare > 0:
if node.right == nil {
node.right = &RedBlackTreeNode{Key: key, Value: value, color: red}
insertedNode = node.right
loop = false
} else {
node = node.right
}
}
}
insertedNode.parent = node
}
tree.insertCase1(insertedNode)
tree.size++
}
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
func (tree *RedBlackTree) Get(key interface{}) (value interface{}) {
value, _ = tree.Search(key)
return
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (tree *RedBlackTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
tree.mu.Lock()
defer tree.mu.Unlock()
if node := tree.doSearch(key); node != nil {
return node.Value
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
tree.doSet(key, value)
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (tree *RedBlackTree) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (tree *RedBlackTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (tree *RedBlackTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (tree *RedBlackTree) GetVar(key interface{}) *gvar.Var {
return gvar.New(tree.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (tree *RedBlackTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(tree.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (tree *RedBlackTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (tree *RedBlackTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *RedBlackTree) SetIfNotExist(key interface{}, value interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *RedBlackTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (tree *RedBlackTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f)
return true
}
return false
}
// Contains checks whether <key> exists in the tree.
func (tree *RedBlackTree) Contains(key interface{}) bool {
_, ok := tree.Search(key)
return ok
}
// doRemove removes the node from the tree by <key> without mutex.
func (tree *RedBlackTree) doRemove(key interface{}) (value interface{}) {
child := (*RedBlackTreeNode)(nil)
node := tree.doSearch(key)
if node == nil {
return
}
value = node.Value
if node.left != nil && node.right != nil {
p := node.left.maximumNode()
node.Key = p.Key
node.Value = p.Value
node = p
}
if node.left == nil || node.right == nil {
if node.right == nil {
child = node.left
} else {
child = node.right
}
if node.color == black {
node.color = tree.nodeColor(child)
tree.deleteCase1(node)
}
tree.replaceNode(node, child)
if node.parent == nil && child != nil {
child.color = black
}
}
tree.size--
return
}
// Remove removes the node from the tree by <key>.
func (tree *RedBlackTree) Remove(key interface{}) (value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
return tree.doRemove(key)
}
// Removes batch deletes values of the tree by <keys>.
func (tree *RedBlackTree) Removes(keys []interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.doRemove(key)
}
}
// IsEmpty returns true if tree does not contain any nodes.
func (tree *RedBlackTree) IsEmpty() bool {
return tree.Size() == 0
}
// Size returns number of nodes in the tree.
func (tree *RedBlackTree) Size() int {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.size
}
// Keys returns all keys in asc order.
func (tree *RedBlackTree) Keys() []interface{} {
keys := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
keys[index] = key
index++
return true
})
return keys
}
// Values returns all values in asc order based on the key.
func (tree *RedBlackTree) Values() []interface{} {
values := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
values[index] = value
index++
return true
})
return values
}
// Map returns all key-value items as map.
func (tree *RedBlackTree) Map() map[interface{}]interface{} {
m := make(map[interface{}]interface{}, tree.Size())
tree.IteratorAsc(func(key, value interface{}) bool {
m[key] = value
return true
})
return m
}
// Left returns the left-most (min) node or nil if tree is empty.
func (tree *RedBlackTree) Left() *RedBlackTreeNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.leftNode()
if tree.mu.IsSafe() {
return &RedBlackTreeNode{
Key: node.Key,
Value: node.Value,
}
}
return node
}
// Right returns the right-most (max) node or nil if tree is empty.
func (tree *RedBlackTree) Right() *RedBlackTreeNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.rightNode()
if tree.mu.IsSafe() {
return &RedBlackTreeNode{
Key: node.Key,
Value: node.Value,
}
}
return node
}
// leftNode returns the left-most (min) node or nil if tree is empty.
func (tree *RedBlackTree) leftNode() *RedBlackTreeNode {
p := (*RedBlackTreeNode)(nil)
n := tree.root
for n != nil {
p = n
n = n.left
}
return p
}
// rightNode returns the right-most (max) node or nil if tree is empty.
func (tree *RedBlackTree) rightNode() *RedBlackTreeNode {
p := (*RedBlackTreeNode)(nil)
n := tree.root
for n != nil {
p = n
n = n.right
}
return p
}
// Floor Finds floor node of the input key, return the floor node or nil if no floor node is found.
// Second return parameter is true if floor was found, otherwise false.
//
// Floor node is defined as the largest node that its key is smaller than or equal to the given <key>.
// A floor node may not be found, either because the tree is empty, or because
// all nodes in the tree are larger than the given node.
func (tree *RedBlackTree) Floor(key interface{}) (floor *RedBlackTreeNode, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
compare := tree.comparator(key, n.Key)
switch {
case compare == 0:
return n, true
case compare < 0:
n = n.left
case compare > 0:
floor, found = n, true
n = n.right
}
}
if found {
return
}
return nil, false
}
// Ceiling finds ceiling node of the input key, return the ceiling node or nil if no ceiling node is found.
// Second return parameter is true if ceiling was found, otherwise false.
//
// Ceiling node is defined as the smallest node that its key is larger than or equal to the given <key>.
// A ceiling node may not be found, either because the tree is empty, or because
// all nodes in the tree are smaller than the given node.
func (tree *RedBlackTree) Ceiling(key interface{}) (ceiling *RedBlackTreeNode, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
compare := tree.comparator(key, n.Key)
switch {
case compare == 0:
return n, true
case compare > 0:
n = n.right
case compare < 0:
ceiling, found = n, true
n = n.left
}
}
if found {
return
}
return nil, false
}
// Iterator is alias of IteratorAsc.
func (tree *RedBlackTree) Iterator(f func(key, value interface{}) bool) {
tree.IteratorAsc(f)
}
// IteratorAsc iterates the tree 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()
defer tree.mu.RUnlock()
node := tree.leftNode()
if node == nil {
return
}
loop:
if node == nil {
return
}
if !f(node.Key, node.Value) {
return
}
if node.right != nil {
node = node.right
for node.left != nil {
node = node.left
}
goto loop
}
if node.parent != nil {
old := node
for node.parent != nil {
node = node.parent
if tree.comparator(old.Key, node.Key) <= 0 {
goto loop
}
}
}
}
// IteratorDesc iterates the tree 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()
defer tree.mu.RUnlock()
node := tree.rightNode()
if node == nil {
return
}
loop:
if node == nil {
return
}
if !f(node.Key, node.Value) {
return
}
if node.left != nil {
node = node.left
for node.right != nil {
node = node.right
}
goto loop
}
if node.parent != nil {
old := node
for node.parent != nil {
node = node.parent
if tree.comparator(old.Key, node.Key) >= 0 {
goto loop
}
}
}
}
// Clear removes all nodes from the tree.
func (tree *RedBlackTree) Clear() {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.root = nil
tree.size = 0
}
// String returns a string representation of container.
func (tree *RedBlackTree) String() string {
tree.mu.RLock()
defer tree.mu.RUnlock()
str := "RedBlackTree\n"
if tree.size != 0 {
tree.output(tree.root, "", true, &str)
}
return str
}
// Print prints the tree to stdout.
func (tree *RedBlackTree) Print() {
fmt.Println(tree.String())
}
// Search searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *RedBlackTree) Search(key interface{}) (value interface{}, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.doSearch(key)
if node != nil {
return node.Value, true
}
return nil, false
}
// Flip exchanges key-value of the tree to value-key.
// Note that you should guarantee the value is the same type as key,
// or else the comparator would panic.
//
// If the type of value is different with key, you pass the new <comparator>.
func (tree *RedBlackTree) Flip(comparator ...func(v1, v2 interface{}) int) {
t := (*RedBlackTree)(nil)
if len(comparator) > 0 {
t = NewRedBlackTree(comparator[0], !tree.mu.IsSafe())
} else {
t = NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
}
tree.IteratorAsc(func(key, value interface{}) bool {
t.doSet(value, key)
return true
})
tree.mu.Lock()
tree.root = t.root
tree.size = t.size
tree.mu.Unlock()
}
func (tree *RedBlackTree) output(node *RedBlackTreeNode, prefix string, isTail bool, str *string) {
if node.right != nil {
newPrefix := prefix
if isTail {
newPrefix += "│ "
} else {
newPrefix += " "
}
tree.output(node.right, newPrefix, false, str)
}
*str += prefix
if isTail {
*str += "└── "
} else {
*str += "┌── "
}
*str += fmt.Sprintf("%v\n", node.Key)
if node.left != nil {
newPrefix := prefix
if isTail {
newPrefix += " "
} else {
newPrefix += "│ "
}
tree.output(node.left, newPrefix, true, str)
}
}
// doSearch searches the tree with given <key> without mutex.
// It returns the node if found or otherwise nil.
func (tree *RedBlackTree) doSearch(key interface{}) *RedBlackTreeNode {
node := tree.root
for node != nil {
compare := tree.comparator(key, node.Key)
switch {
case compare == 0:
return node
case compare < 0:
node = node.left
case compare > 0:
node = node.right
}
}
return nil
}
func (node *RedBlackTreeNode) grandparent() *RedBlackTreeNode {
if node != nil && node.parent != nil {
return node.parent.parent
}
return nil
}
func (node *RedBlackTreeNode) uncle() *RedBlackTreeNode {
if node == nil || node.parent == nil || node.parent.parent == nil {
return nil
}
return node.parent.sibling()
}
func (node *RedBlackTreeNode) sibling() *RedBlackTreeNode {
if node == nil || node.parent == nil {
return nil
}
if node == node.parent.left {
return node.parent.right
}
return node.parent.left
}
func (tree *RedBlackTree) rotateLeft(node *RedBlackTreeNode) {
right := node.right
tree.replaceNode(node, right)
node.right = right.left
if right.left != nil {
right.left.parent = node
}
right.left = node
node.parent = right
}
func (tree *RedBlackTree) rotateRight(node *RedBlackTreeNode) {
left := node.left
tree.replaceNode(node, left)
node.left = left.right
if left.right != nil {
left.right.parent = node
}
left.right = node
node.parent = left
}
func (tree *RedBlackTree) replaceNode(old *RedBlackTreeNode, new *RedBlackTreeNode) {
if old.parent == nil {
tree.root = new
} else {
if old == old.parent.left {
old.parent.left = new
} else {
old.parent.right = new
}
}
if new != nil {
new.parent = old.parent
}
}
func (tree *RedBlackTree) insertCase1(node *RedBlackTreeNode) {
if node.parent == nil {
node.color = black
} else {
tree.insertCase2(node)
}
}
func (tree *RedBlackTree) insertCase2(node *RedBlackTreeNode) {
if tree.nodeColor(node.parent) == black {
return
}
tree.insertCase3(node)
}
func (tree *RedBlackTree) insertCase3(node *RedBlackTreeNode) {
uncle := node.uncle()
if tree.nodeColor(uncle) == red {
node.parent.color = black
uncle.color = black
node.grandparent().color = red
tree.insertCase1(node.grandparent())
} else {
tree.insertCase4(node)
}
}
func (tree *RedBlackTree) insertCase4(node *RedBlackTreeNode) {
grandparent := node.grandparent()
if node == node.parent.right && node.parent == grandparent.left {
tree.rotateLeft(node.parent)
node = node.left
} else if node == node.parent.left && node.parent == grandparent.right {
tree.rotateRight(node.parent)
node = node.right
}
tree.insertCase5(node)
}
func (tree *RedBlackTree) insertCase5(node *RedBlackTreeNode) {
node.parent.color = black
grandparent := node.grandparent()
grandparent.color = red
if node == node.parent.left && node.parent == grandparent.left {
tree.rotateRight(grandparent)
} else if node == node.parent.right && node.parent == grandparent.right {
tree.rotateLeft(grandparent)
}
}
func (node *RedBlackTreeNode) maximumNode() *RedBlackTreeNode {
if node == nil {
return nil
}
for node.right != nil {
return node.right
}
return node
}
func (tree *RedBlackTree) deleteCase1(node *RedBlackTreeNode) {
if node.parent == nil {
return
}
tree.deleteCase2(node)
}
func (tree *RedBlackTree) deleteCase2(node *RedBlackTreeNode) {
sibling := node.sibling()
if tree.nodeColor(sibling) == red {
node.parent.color = red
sibling.color = black
if node == node.parent.left {
tree.rotateLeft(node.parent)
} else {
tree.rotateRight(node.parent)
}
}
tree.deleteCase3(node)
}
func (tree *RedBlackTree) deleteCase3(node *RedBlackTreeNode) {
sibling := node.sibling()
if tree.nodeColor(node.parent) == black &&
tree.nodeColor(sibling) == black &&
tree.nodeColor(sibling.left) == black &&
tree.nodeColor(sibling.right) == black {
sibling.color = red
tree.deleteCase1(node.parent)
} else {
tree.deleteCase4(node)
}
}
func (tree *RedBlackTree) deleteCase4(node *RedBlackTreeNode) {
sibling := node.sibling()
if tree.nodeColor(node.parent) == red &&
tree.nodeColor(sibling) == black &&
tree.nodeColor(sibling.left) == black &&
tree.nodeColor(sibling.right) == black {
sibling.color = red
node.parent.color = black
} else {
tree.deleteCase5(node)
}
}
func (tree *RedBlackTree) deleteCase5(node *RedBlackTreeNode) {
sibling := node.sibling()
if node == node.parent.left &&
tree.nodeColor(sibling) == black &&
tree.nodeColor(sibling.left) == red &&
tree.nodeColor(sibling.right) == black {
sibling.color = red
sibling.left.color = black
tree.rotateRight(sibling)
} else if node == node.parent.right &&
tree.nodeColor(sibling) == black &&
tree.nodeColor(sibling.right) == red &&
tree.nodeColor(sibling.left) == black {
sibling.color = red
sibling.right.color = black
tree.rotateLeft(sibling)
}
tree.deleteCase6(node)
}
func (tree *RedBlackTree) deleteCase6(node *RedBlackTreeNode) {
sibling := node.sibling()
sibling.color = tree.nodeColor(node.parent)
node.parent.color = black
if node == node.parent.left && tree.nodeColor(sibling.right) == red {
sibling.right.color = black
tree.rotateLeft(node.parent)
} else if tree.nodeColor(sibling.left) == red {
sibling.left.color = black
tree.rotateRight(node.parent)
}
}
func (tree *RedBlackTree) nodeColor(node *RedBlackTreeNode) color {
if node == nil {
return black
}
return node.color
}

View File

@ -0,0 +1,250 @@
// Copyright 2017-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 gtree_test
import (
"fmt"
"testing"
"github.com/gogf/gf/g/container/gtree"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gutil"
)
func Test_AVLTree_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewAVLTree(gutil.ComparatorString)
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Sets(map[interface{}]interface{}{"key3": "val3", "key1": "val1"})
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Flip(gutil.ComparatorString)
gtest.Assert(m.Map(), map[interface{}]interface{}{"key3": "val3", "key1": "val1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gtree.NewAVLTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_AVLTree_Set_Fun(t *testing.T) {
//GetOrSetFunc lock or unlock
gtest.Case(t, func() {
m := gtree.NewAVLTree(gutil.ComparatorString)
gtest.Assert(m.GetOrSetFunc("fun", getValue), 3)
gtest.Assert(m.GetOrSetFunc("fun", getValue), 3)
gtest.Assert(m.GetOrSetFuncLock("funlock", getValue), 3)
gtest.Assert(m.GetOrSetFuncLock("funlock", getValue), 3)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
})
//SetIfNotExistFunc lock or unlock
gtest.Case(t, func() {
m := gtree.NewAVLTree(gutil.ComparatorString)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), true)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), true)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
})
}
func Test_AVLTree_Get_Set_Var(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewAVLTree(gutil.ComparatorString)
gtest.AssertEQ(m.SetIfNotExist("key1", "val1"), true)
gtest.AssertEQ(m.SetIfNotExist("key1", "val1"), false)
gtest.AssertEQ(m.GetVarOrSet("key1", "val1"), gvar.New("val1", true))
gtest.AssertEQ(m.GetVar("key1"), gvar.New("val1", true))
})
gtest.Case(t, func() {
m := gtree.NewAVLTree(gutil.ComparatorString)
gtest.AssertEQ(m.GetVarOrSetFunc("fun", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFunc("fun", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFuncLock("funlock", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFuncLock("funlock", getValue), gvar.New(3, true))
})
}
func Test_AVLTree_Batch(t *testing.T) {
m := gtree.NewAVLTree(gutil.ComparatorString)
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_AVLTree_Iterator(t *testing.T) {
keys := []string{"1", "key1", "key2", "key3", "key4"}
keyLen := len(keys)
index := 0
expect := map[interface{}]interface{}{"key4": "val4", 1: 1, "key1": "val1", "key2": "val2", "key3": "val3"}
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(k, keys[index])
index++
gtest.Assert(expect[k], v)
return true
})
m.IteratorDesc(func(k interface{}, v interface{}) bool {
index--
gtest.Assert(k, keys[index])
gtest.Assert(expect[k], v)
return true
})
m.Print()
// 断言返回值对遍历控制
gtest.Case(t, func() {
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, keyLen)
gtest.Assert(j, 1)
})
gtest.Case(t, func() {
i := 0
j := 0
m.IteratorDesc(func(k interface{}, v interface{}) bool {
i++
return true
})
m.IteratorDesc(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, keyLen)
gtest.Assert(j, 1)
})
}
func Test_AVLTree_Clone(t *testing.T) {
//clone 方法是深克隆
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}
func Test_AVLTree_LRNode(t *testing.T) {
expect := map[interface{}]interface{}{"key4": "val4", "key1": "val1", "key2": "val2", "key3": "val3"}
//safe
gtest.Case(t, func() {
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, expect)
gtest.Assert(m.Left().Key, "key1")
gtest.Assert(m.Right().Key, "key4")
})
//unsafe
gtest.Case(t, func() {
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, expect, true)
gtest.Assert(m.Left().Key, "key1")
gtest.Assert(m.Right().Key, "key4")
})
}
func Test_AVLTree_CeilingFloor(t *testing.T) {
expect := map[interface{}]interface{}{
20: "val20",
6: "val6",
10: "val10",
12: "val12",
1: "val1",
15: "val15",
19: "val19",
8: "val8",
4: "val4"}
//found and eq
gtest.Case(t, func() {
m := gtree.NewAVLTreeFrom(gutil.ComparatorInt, expect)
c, cf := m.Ceiling(8)
gtest.Assert(cf, true)
gtest.Assert(c.Value, "val8")
f, ff := m.Floor(20)
gtest.Assert(ff, true)
gtest.Assert(f.Value, "val20")
})
//found and neq
gtest.Case(t, func() {
m := gtree.NewAVLTreeFrom(gutil.ComparatorInt, expect)
c, cf := m.Ceiling(9)
gtest.Assert(cf, true)
gtest.Assert(c.Value, "val10")
f, ff := m.Floor(5)
gtest.Assert(ff, true)
gtest.Assert(f.Value, "val4")
})
//nofound
gtest.Case(t, func() {
m := gtree.NewAVLTreeFrom(gutil.ComparatorInt, expect)
c, cf := m.Ceiling(21)
gtest.Assert(cf, false)
gtest.Assert(c, nil)
f, ff := m.Floor(-1)
gtest.Assert(ff, false)
gtest.Assert(f, nil)
})
}
func Test_AVLTree_Remove(t *testing.T) {
m := gtree.NewAVLTree(gutil.ComparatorInt)
for i := 1; i <= 50; i++ {
m.Set(i, fmt.Sprintf("val%d", i))
}
expect := m.Map()
gtest.Case(t, func() {
for k, v := range expect {
m1 := m.Clone()
gtest.Assert(m1.Remove(k), v)
gtest.Assert(m1.Remove(k), nil)
}
})
}

View File

@ -0,0 +1,202 @@
// Copyright 2017-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 gtree_test
import (
"fmt"
"testing"
"github.com/gogf/gf/g/container/gtree"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gutil"
)
func Test_BTree_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewBTree(3, gutil.ComparatorString)
m.Set("key1", "val1")
gtest.Assert(m.Height(), 1)
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gtree.NewBTreeFrom(3, gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_BTree_Set_Fun(t *testing.T) {
//GetOrSetFunc lock or unlock
gtest.Case(t, func() {
m := gtree.NewBTree(3, gutil.ComparatorString)
gtest.Assert(m.GetOrSetFunc("fun", getValue), 3)
gtest.Assert(m.GetOrSetFunc("fun", getValue), 3)
gtest.Assert(m.GetOrSetFuncLock("funlock", getValue), 3)
gtest.Assert(m.GetOrSetFuncLock("funlock", getValue), 3)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
})
//SetIfNotExistFunc lock or unlock
gtest.Case(t, func() {
m := gtree.NewBTree(3, gutil.ComparatorString)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), true)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), true)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
})
}
func Test_BTree_Get_Set_Var(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewBTree(3, gutil.ComparatorString)
gtest.AssertEQ(m.SetIfNotExist("key1", "val1"), true)
gtest.AssertEQ(m.SetIfNotExist("key1", "val1"), false)
gtest.AssertEQ(m.GetVarOrSet("key1", "val1"), gvar.New("val1", true))
gtest.AssertEQ(m.GetVar("key1"), gvar.New("val1", true))
})
gtest.Case(t, func() {
m := gtree.NewBTree(3, gutil.ComparatorString)
gtest.AssertEQ(m.GetVarOrSetFunc("fun", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFunc("fun", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFuncLock("funlock", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFuncLock("funlock", getValue), gvar.New(3, true))
})
}
func Test_BTree_Batch(t *testing.T) {
m := gtree.NewBTree(3, gutil.ComparatorString)
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_BTree_Iterator(t *testing.T) {
keys := []string{"1", "key1", "key2", "key3", "key4"}
keyLen := len(keys)
index := 0
expect := map[interface{}]interface{}{"key4": "val4", 1: 1, "key1": "val1", "key2": "val2", "key3": "val3"}
m := gtree.NewBTreeFrom(3, gutil.ComparatorString, expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(k, keys[index])
index++
gtest.Assert(expect[k], v)
return true
})
m.IteratorDesc(func(k interface{}, v interface{}) bool {
index--
gtest.Assert(k, keys[index])
gtest.Assert(expect[k], v)
return true
})
m.Print()
// 断言返回值对遍历控制
gtest.Case(t, func() {
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, keyLen)
gtest.Assert(j, 1)
})
gtest.Case(t, func() {
i := 0
j := 0
m.IteratorDesc(func(k interface{}, v interface{}) bool {
i++
return true
})
m.IteratorDesc(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, keyLen)
gtest.Assert(j, 1)
})
}
func Test_BTree_Clone(t *testing.T) {
//clone 方法是深克隆
m := gtree.NewBTreeFrom(3, gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}
func Test_BTree_LRNode(t *testing.T) {
expect := map[interface{}]interface{}{"key4": "val4", "key1": "val1", "key2": "val2", "key3": "val3"}
//safe
gtest.Case(t, func() {
m := gtree.NewBTreeFrom(3, gutil.ComparatorString, expect)
gtest.Assert(m.Left().Key, "key1")
gtest.Assert(m.Right().Key, "key4")
})
//unsafe
gtest.Case(t, func() {
m := gtree.NewBTreeFrom(3, gutil.ComparatorString, expect, true)
gtest.Assert(m.Left().Key, "key1")
gtest.Assert(m.Right().Key, "key4")
})
}
func Test_BTree_Remove(t *testing.T) {
m := gtree.NewBTree(3, gutil.ComparatorInt)
for i := 1; i <= 100; i++ {
m.Set(i, fmt.Sprintf("val%d", i))
}
expect := m.Map()
gtest.Case(t, func() {
for k, v := range expect {
m1 := m.Clone()
gtest.Assert(m1.Remove(k), v)
gtest.Assert(m1.Remove(k), nil)
}
})
}

View File

@ -0,0 +1,255 @@
// Copyright 2017-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 gtree_test
import (
"fmt"
"testing"
"github.com/gogf/gf/g/container/gtree"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gutil"
)
func getValue() interface{} {
return 3
}
func Test_RedBlackTree_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Sets(map[interface{}]interface{}{"key3": "val3", "key1": "val1"})
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Flip(gutil.ComparatorString)
gtest.Assert(m.Map(), map[interface{}]interface{}{"key3": "val3", "key1": "val1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gtree.NewRedBlackTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_RedBlackTree_Set_Fun(t *testing.T) {
//GetOrSetFunc lock or unlock
gtest.Case(t, func() {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
gtest.Assert(m.GetOrSetFunc("fun", getValue), 3)
gtest.Assert(m.GetOrSetFunc("fun", getValue), 3)
gtest.Assert(m.GetOrSetFuncLock("funlock", getValue), 3)
gtest.Assert(m.GetOrSetFuncLock("funlock", getValue), 3)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
})
//SetIfNotExistFunc lock or unlock
gtest.Case(t, func() {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), true)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), true)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
})
}
func Test_RedBlackTree_Get_Set_Var(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
gtest.AssertEQ(m.SetIfNotExist("key1", "val1"), true)
gtest.AssertEQ(m.SetIfNotExist("key1", "val1"), false)
gtest.AssertEQ(m.GetVarOrSet("key1", "val1"), gvar.New("val1", true))
gtest.AssertEQ(m.GetVar("key1"), gvar.New("val1", true))
})
gtest.Case(t, func() {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
gtest.AssertEQ(m.GetVarOrSetFunc("fun", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFunc("fun", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFuncLock("funlock", getValue), gvar.New(3, true))
gtest.AssertEQ(m.GetVarOrSetFuncLock("funlock", getValue), gvar.New(3, true))
})
}
func Test_RedBlackTree_Batch(t *testing.T) {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_RedBlackTree_Iterator(t *testing.T) {
keys := []string{"1", "key1", "key2", "key3", "key4"}
keyLen := len(keys)
index := 0
expect := map[interface{}]interface{}{"key4": "val4", 1: 1, "key1": "val1", "key2": "val2", "key3": "val3"}
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorString, expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(k, keys[index])
index++
gtest.Assert(expect[k], v)
return true
})
m.IteratorDesc(func(k interface{}, v interface{}) bool {
index--
gtest.Assert(k, keys[index])
gtest.Assert(expect[k], v)
return true
})
m.Print()
// 断言返回值对遍历控制
gtest.Case(t, func() {
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, keyLen)
gtest.Assert(j, 1)
})
gtest.Case(t, func() {
i := 0
j := 0
m.IteratorDesc(func(k interface{}, v interface{}) bool {
i++
return true
})
m.IteratorDesc(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, keyLen)
gtest.Assert(j, 1)
})
}
func Test_RedBlackTree_Clone(t *testing.T) {
//clone 方法是深克隆
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}
func Test_RedBlackTree_LRNode(t *testing.T) {
expect := map[interface{}]interface{}{"key4": "val4", "key1": "val1", "key2": "val2", "key3": "val3"}
//safe
gtest.Case(t, func() {
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorString, expect)
gtest.Assert(m.Left().Key, "key1")
gtest.Assert(m.Right().Key, "key4")
})
//unsafe
gtest.Case(t, func() {
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorString, expect, true)
gtest.Assert(m.Left().Key, "key1")
gtest.Assert(m.Right().Key, "key4")
})
}
func Test_RedBlackTree_CeilingFloor(t *testing.T) {
expect := map[interface{}]interface{}{
20: "val20",
6: "val6",
10: "val10",
12: "val12",
1: "val1",
15: "val15",
19: "val19",
8: "val8",
4: "val4"}
//found and eq
gtest.Case(t, func() {
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorInt, expect)
c, cf := m.Ceiling(8)
gtest.Assert(cf, true)
gtest.Assert(c.Value, "val8")
f, ff := m.Floor(20)
gtest.Assert(ff, true)
gtest.Assert(f.Value, "val20")
})
//found and neq
gtest.Case(t, func() {
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorInt, expect)
c, cf := m.Ceiling(9)
gtest.Assert(cf, true)
gtest.Assert(c.Value, "val10")
f, ff := m.Floor(5)
gtest.Assert(ff, true)
gtest.Assert(f.Value, "val4")
})
//nofound
gtest.Case(t, func() {
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorInt, expect)
c, cf := m.Ceiling(21)
gtest.Assert(cf, false)
gtest.Assert(c, nil)
f, ff := m.Floor(-1)
gtest.Assert(ff, false)
gtest.Assert(f, nil)
})
}
func Test_RedBlackTree_Remove(t *testing.T) {
m := gtree.NewRedBlackTree(gutil.ComparatorInt)
for i := 1; i <= 100; i++ {
m.Set(i, fmt.Sprintf("val%d", i))
}
expect := m.Map()
gtest.Case(t, func() {
for k, v := range expect {
m1 := m.Clone()
gtest.Assert(m1.Remove(k), v)
gtest.Assert(m1.Remove(k), nil)
}
})
}

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