From 85677b952f66ed9e9792788c33c2f63360addbdb Mon Sep 17 00:00:00 2001 From: John Date: Wed, 30 Jan 2019 21:27:03 +0800 Subject: [PATCH] fulfil funcs of gstr/gset/garray; move gstr/gregex to the string category; move gtest to the test category --- README.MD | 12 +- README_ZH.MD | 18 +- RELEASE.MD | 36 +- TODO.MD | 5 + g/container/garray/garray.go | 2 +- g/container/garray/garray_func.go | 8 + g/container/garray/garray_int.go | 176 ------ g/container/garray/garray_interface.go | 194 ------ g/container/garray/garray_normal_int.go | 394 ++++++++++++ g/container/garray/garray_normal_interface.go | 393 ++++++++++++ g/container/garray/garray_normal_string.go | 394 ++++++++++++ g/container/garray/garray_sorted_int.go | 143 ++++- g/container/garray/garray_sorted_interface.go | 151 ++++- g/container/garray/garray_sorted_string.go | 143 ++++- g/container/garray/garray_string.go | 175 ------ ...it_test.go => garray_z_unit_basic_test.go} | 3 +- g/container/garray/garray_z_unit_int_test.go | 161 +++++ g/container/glist/glist.go | 2 +- g/container/gmap/gmap.go | 2 +- g/container/gmap/gmap_func.go | 8 + g/container/gmap/gmap_int_bool_map.go | 13 + g/container/gmap/gmap_int_int_map.go | 24 + g/container/gmap/gmap_int_interface_map.go | 29 +- g/container/gmap/gmap_int_string_map.go | 25 + .../gmap/gmap_interface_interface_map.go | 24 + g/container/gmap/gmap_string_bool_map.go | 13 + g/container/gmap/gmap_string_int_map.go | 29 +- g/container/gmap/gmap_string_interface_map.go | 25 + g/container/gmap/gmap_string_string_map.go | 28 +- ...safe_test.go => gmap_z_bench_safe_test.go} | 0 ...p_test.go => gmap_z_bench_syncmap_test.go} | 0 ...fe_test.go => gmap_z_bench_unsafe_test.go} | 0 g/container/gqueue/gqueue.go | 2 +- g/container/gset/gset_int_set.go | 129 +++- g/container/gset/gset_interface_set.go | 126 +++- g/container/gset/gset_string_set.go | 126 +++- .../{gset_test.go => gset_z_bench_test.go} | 0 ...fe_test.go => gset_z_bench_unsafe_test.go} | 0 g/container/gset/gset_z_unit_test.go | 25 + g/crypto/gdes/gdes.go | 32 +- g/crypto/gsha1/gsha1.go | 1 + g/database/gdb/gdb_base.go | 2 +- g/database/gdb/gdb_func.go | 4 +- g/database/gdb/gdb_mssql.go | 2 +- g/database/gdb/gdb_oracle.go | 2 +- g/database/gdb/gdb_structure.go | 2 +- g/database/gdb/gdb_transaction.go | 2 +- g/database/gdb/gdb_unit_0_test.go | 2 +- g/database/gdb/gdb_unit_1_test.go | 2 +- g/database/gdb/gdb_unit_2_test.go | 2 +- g/database/gdb/gdb_unit_3_test.go | 2 +- g/encoding/gjson/gjson.go | 4 +- g/encoding/gurl/url.go | 62 +- g/encoding/gxml/gxml.go | 2 +- g/frame/gins/gins.go | 2 +- g/net/ghttp/ghttp_client_request_client.go | 2 +- g/net/ghttp/ghttp_request.go | 2 +- g/net/ghttp/ghttp_server.go | 2 +- g/net/ghttp/ghttp_server_router.go | 4 +- g/net/ghttp/ghttp_server_router_hook.go | 2 +- g/net/ghttp/ghttp_server_router_serve.go | 2 +- .../ghttp/ghttp_server_service_controller.go | 2 +- g/net/ghttp/ghttp_server_service_handler.go | 2 +- g/net/ghttp/ghttp_server_service_object.go | 2 +- g/net/ghttp/ghttp_unit_1_test.go | 2 +- g/net/ghttp/ghttp_unit_2_test.go | 2 +- g/net/ghttp/ghttp_unit_3_test.go | 2 +- g/net/gipv4/gipv4.go | 68 ++- g/net/gipv6/gipv6.go | 2 +- g/os/gcache/gcache_z_unit_1_test.go | 2 +- g/os/gcron/gcron_schedule.go | 2 +- g/os/gcron/gcron_unit_1_test.go | 2 +- g/os/gcron/gcron_unit_2_test.go | 2 +- g/os/gfile/gfile.go | 18 +- g/os/glog/glog_logger.go | 2 +- g/os/gmlock/gmlock_unit_lock_test.go | 2 +- g/os/gmlock/gmlock_unit_rlock_test.go | 2 +- g/os/gspath/gspath.go | 2 +- g/os/gtime/gtime.go | 4 +- g/os/gtime/gtime_format.go | 2 +- g/os/gtimer/gtimer_z_unit_0_test.go | 2 +- g/os/gtimer/gtimer_z_unit_1_test.go | 2 +- g/os/gtimer/gtimer_z_unit_2_test.go | 2 +- g/os/gview/gview.go | 2 +- g/{util => string}/gregex/gregex.go | 0 .../gregex/gregex_z_bench_test.go} | 0 g/string/gstr/gstr.go | 576 ++++++++++++++++++ g/string/gstr/gstr_levenshtein.go | 55 ++ g/string/gstr/gstr_parse.go | 157 +++++ g/string/gstr/gstr_pos.go | 76 +++ g/string/gstr/gstr_similartext.go | 47 ++ g/string/gstr/gstr_soundex.go | 52 ++ .../gstr/gstr_z_bench_test.go} | 0 g/{util => test}/gtest/gtest.go | 20 +- g/util/gconv/gconv.go | 161 ++--- g/util/gconv/gconv_struct.go | 2 +- g/util/gconv/gconv_time.go | 2 +- g/util/gconv/gconv_z_unit_basic_test.go | 2 +- g/util/gconv/gconv_z_unit_map_test.go | 2 +- g/util/gconv/gconv_z_unit_slice_test.go | 2 +- g/util/gconv/gconv_z_unit_struct_test.go | 14 +- g/util/gconv/gconv_z_unit_time_test.go | 2 +- g/util/gpage/gpage.go | 4 +- g/util/grand/grand.go | 29 +- g/util/grand/grand_intn.go | 4 +- g/util/gstr/gstr.go | 184 ------ g/util/gutil/gutil.go | 5 +- g/util/gvalid/gvalid.go | 2 +- g/util/gvalid/gvalid_check.go | 2 +- g/util/gvalid/gvalid_unit_basic_all_test.go | 2 +- geg/container/gpool/gpool.go | 2 +- .../server/hooks/same_route_multi_hook.go | 2 +- geg/os/gview/build_in_funcs/build_in_funcs.go | 4 +- geg/other/test.go | 21 +- geg/util/gconv/gconv_struct1.go | 4 +- geg/util/gconv/gconv_struct2.go | 2 +- geg/util/gpage/gpage_custom1.go | 2 +- geg/util/gstr/gstr_hidestr.go | 2 +- geg/util/gstr/gstr_substr.go | 2 +- third/README.MD | 2 +- 120 files changed, 3695 insertions(+), 1057 deletions(-) create mode 100644 g/container/garray/garray_func.go delete mode 100644 g/container/garray/garray_int.go delete mode 100644 g/container/garray/garray_interface.go create mode 100644 g/container/garray/garray_normal_int.go create mode 100644 g/container/garray/garray_normal_interface.go create mode 100644 g/container/garray/garray_normal_string.go delete mode 100644 g/container/garray/garray_string.go rename g/container/garray/{garray_z_unit_test.go => garray_z_unit_basic_test.go} (98%) create mode 100644 g/container/garray/garray_z_unit_int_test.go create mode 100644 g/container/gmap/gmap_func.go rename g/container/gmap/{gmap_bench_safe_test.go => gmap_z_bench_safe_test.go} (100%) rename g/container/gmap/{gmap_bench_syncmap_test.go => gmap_z_bench_syncmap_test.go} (100%) rename g/container/gmap/{gmap_bench_unsafe_test.go => gmap_z_bench_unsafe_test.go} (100%) rename g/container/gset/{gset_test.go => gset_z_bench_test.go} (100%) rename g/container/gset/{gset_unsafe_test.go => gset_z_bench_unsafe_test.go} (100%) create mode 100644 g/container/gset/gset_z_unit_test.go rename g/{util => string}/gregex/gregex.go (100%) rename g/{util/gregex/gregex_test.go => string/gregex/gregex_z_bench_test.go} (100%) create mode 100644 g/string/gstr/gstr.go create mode 100644 g/string/gstr/gstr_levenshtein.go create mode 100644 g/string/gstr/gstr_parse.go create mode 100644 g/string/gstr/gstr_pos.go create mode 100644 g/string/gstr/gstr_similartext.go create mode 100644 g/string/gstr/gstr_soundex.go rename g/{util/gstr/gstr_test.go => string/gstr/gstr_z_bench_test.go} (100%) rename g/{util => test}/gtest/gtest.go (92%) delete mode 100644 g/util/gstr/gstr.go diff --git a/README.MD b/README.MD index ff188dd89..afe2d6fc0 100644 --- a/README.MD +++ b/README.MD @@ -1,10 +1,10 @@ # GoFrame - + [![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) +[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://goframe.org) [![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) @@ -36,7 +36,7 @@ golang version >= 1.9.2 # Architecture
- +
# Quick Start @@ -58,7 +58,7 @@ func main() { } ``` -[View More..](https://gfer.me/start/index) +[View More..](https://goframe.org/start/index) # License @@ -71,7 +71,7 @@ func main() { - + @@ -84,7 +84,7 @@ func main() { - + diff --git a/README_ZH.MD b/README_ZH.MD index fb8d57425..be823d7ab 100644 --- a/README_ZH.MD +++ b/README_ZH.MD @@ -1,10 +1,10 @@ # GoFrame - + [![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) +[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://goframe.org) [![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) @@ -14,10 +14,8 @@ [![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.com/gogf/gf) --> -`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等特性。 # 特点 @@ -45,7 +43,7 @@ golang版本 >= 1.9.2 # 架构
- +
@@ -74,7 +72,7 @@ func main() { } ``` -[更多..](https://gfer.me/start/index) +[更多..](https://goframe.org/start/index) # 协议 @@ -86,14 +84,14 @@ func main() { - + - +


diff --git a/RELEASE.MD b/RELEASE.MD index 06c0b61ef..7c056eb3a 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -31,20 +31,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 +67,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 +88,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,7 +99,7 @@ 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`下搜索失效问题; diff --git a/TODO.MD b/TODO.MD index 98de17c6f..c34ae28f8 100644 --- a/TODO.MD +++ b/TODO.MD @@ -47,6 +47,11 @@ 1. 增加jumplist的数据结构容器; 1. DelayQueue/PriorityQueue; 1. gconv针对struct的转换增加json tag支持,gconv.Map默认也支持json tag; +1. 改进gproc进程间通信处理逻辑,提高稳定性,以应对进程间大批量的数据发送/接收; + + + + # DONE 1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换; diff --git a/g/container/garray/garray.go b/g/container/garray/garray.go index 2f87a97b9..7c3f044e5 100644 --- a/g/container/garray/garray.go +++ b/g/container/garray/garray.go @@ -6,7 +6,7 @@ // Package garray provides kinds of concurrent-safe(alternative) arrays. // -// 并发安全的数组. +// 并发安全数组. package garray func New(size int, cap int, unsafe...bool) *Array { diff --git a/g/container/garray/garray_func.go b/g/container/garray/garray_func.go new file mode 100644 index 000000000..9d7a14508 --- /dev/null +++ b/g/container/garray/garray_func.go @@ -0,0 +1,8 @@ +// Copyright 2019 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 + diff --git a/g/container/garray/garray_int.go b/g/container/garray/garray_int.go deleted file mode 100644 index a58919018..000000000 --- a/g/container/garray/garray_int.go +++ /dev/null @@ -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) -} diff --git a/g/container/garray/garray_interface.go b/g/container/garray/garray_interface.go deleted file mode 100644 index ba5e927fe..000000000 --- a/g/container/garray/garray_interface.go +++ /dev/null @@ -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) -} diff --git a/g/container/garray/garray_normal_int.go b/g/container/garray/garray_normal_int.go new file mode 100644 index 000000000..740386e80 --- /dev/null +++ b/g/container/garray/garray_normal_int.go @@ -0,0 +1,394 @@ +// 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" + "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/util/grand" + "math" + "sort" + "strings" +) + +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 NewIntArrayFrom(array []int, unsafe...bool) *IntArray { + return &IntArray{ + mu : rwmutex.New(unsafe...), + array : array, + } +} + +// 获取指定索引的数据项, 调用方注意判断数组边界 +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) *IntArray { + a.mu.Lock() + defer a.mu.Unlock() + a.array[index] = value + return a +} + +// 设置底层数组变量. +func (a *IntArray) SetArray(array []int) *IntArray { + a.mu.Lock() + defer a.mu.Unlock() + a.array = array + return a +} + +// 将数组重新排序(从小到大). +func (a *IntArray) Sort() *IntArray { + a.mu.Lock() + defer a.mu.Unlock() + sort.Ints(a.array) + return a +} + +// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界 +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 +} + +// 在当前索引位置后插入一个数据项, 调用方注意判断数组边界 +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 +} + +// 删除指定索引的数据项, 调用方注意判断数组边界 +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 +} + +// 将数据项添加到数组的最左端(索引为0) +func (a *IntArray) PushLeft(value...int) *IntArray { + a.mu.Lock() + a.array = append(value, a.array...) + a.mu.Unlock() + return a +} + +// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append +func (a *IntArray) PushRight(value...int) *IntArray { + a.mu.Lock() + a.array = append(a.array, value...) + a.mu.Unlock() + return a +} + +// 将最左端(索引为0)的数据项移出数组,并返回该数据项 +func (a *IntArray) PopLeft() int { + a.mu.Lock() + defer a.mu.Unlock() + value := a.array[0] + a.array = a.array[1 : ] + return value +} + +// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项 +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 +} + +// 随机将一个数据项移出数组,并返回该数据项 +func (a *IntArray) PopRand() int { + return a.Remove(grand.Intn(len(a.array))) +} + +// 追加数据项 +func (a *IntArray) Append(value...int) *IntArray { + a.mu.Lock() + a.array = append(a.array, value...) + a.mu.Unlock() + return a +} + +// 数组长度 +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() + defer a.mu.RUnlock() + array = make([]int, len(a.array)) + copy(array, a.array) + } else { + array = a.array + } + return array +} + +// 清空数据数组 +func (a *IntArray) Clear() *IntArray { + 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() + return a +} + +// 查找指定数值是否存在 +func (a *IntArray) Contains(value int) bool { + return a.Search(value) != -1 +} + +// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-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)) *IntArray { + a.mu.Lock(true) + defer a.mu.Unlock(true) + f(a.array) + return a +} + +// 使用自定义方法执行加锁读取操作 +func (a *IntArray) RLockFunc(f func(array []int)) *IntArray { + a.mu.RLock(true) + defer a.mu.RUnlock(true) + f(a.array) + return a +} + +// 合并两个数组. +func (a *IntArray) Merge(array *IntArray) *IntArray { + a.mu.Lock() + defer a.mu.Unlock() + if a != array { + array.mu.RLock() + defer array.mu.RUnlock() + } + a.array = append(a.array, array.array...) + return a +} + +// Fills an array with num entries of the value of the value parameter, 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 +} + +// Chunks an array into arrays with size elements. The last chunk may contain less than size elements. +func (a *IntArray) Chunk(size int) [][]int { + if size < 1 { + panic("size: cannot be less than 1") + } + 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 array to the specified length with a 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 +} + +// Extract a slice of the array(If in concurrent safe usage, it returns a copy of the slice; else a pointer). +// It returns the sequence of elements from the array array as specified by the offset and length parameters. +func (a *IntArray) SubSlice(offset, size int) []int { + a.mu.RLock() + defer a.mu.RUnlock() + if offset > len(a.array) { + return nil + } + if offset + size > 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:] + } +} + +// Picks one or more random entries out of an array(a copy), and returns the key (or keys) of the random entries. +func (a *IntArray) Rand(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 +} + +// 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 +} + +// Make 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 array elements with a string. +func (a *IntArray) Join(glue string) string { + a.mu.RLock() + defer a.mu.RUnlock() + return strings.Join(gconv.Strings(a.array), glue) +} \ No newline at end of file diff --git a/g/container/garray/garray_normal_interface.go b/g/container/garray/garray_normal_interface.go new file mode 100644 index 000000000..3425c4b2c --- /dev/null +++ b/g/container/garray/garray_normal_interface.go @@ -0,0 +1,393 @@ +// 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" + "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/util/grand" + "math" + "sort" + "strings" +) + +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 NewArrayFrom(array []interface{}, unsafe...bool) *Array { + return &Array{ + mu : rwmutex.New(unsafe...), + array : array, + } +} + +// 获取指定索引的数据项, 调用方注意判断数组边界 +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{}) *Array { + a.mu.Lock() + defer a.mu.Unlock() + a.array[index] = value + return a +} + +// 设置底层数组变量. +func (a *Array) SetArray(array []interface{}) *Array { + a.mu.Lock() + defer a.mu.Unlock() + a.array = array + return a +} + +// 将数组重新排序(从小到大). +func (a *Array) Sort(less func(i, j int) bool) *Array { + a.mu.Lock() + defer a.mu.Unlock() + sort.Slice(a.array, less) + return a +} + +// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界 +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 +} + +// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界 +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 +} + +// 删除指定索引的数据项, 调用方注意判断数组边界 +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) PushLeft(value...interface{}) *Array { + a.mu.Lock() + a.array = append(value, a.array...) + a.mu.Unlock() + return a +} + +// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append +func (a *Array) PushRight(value...interface{}) *Array { + a.mu.Lock() + a.array = append(a.array, value...) + a.mu.Unlock() + return a +} + +// 将最左端(索引为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) PopRand() interface{} { + return a.Remove(grand.Intn(len(a.array))) +} + +// 追加数据项, 等于: PushRight +func (a *Array) Append(value...interface{}) *Array { + a.PushRight(value...) + return a +} + +// 数组长度 +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() + defer a.mu.RUnlock() + array = make([]interface{}, len(a.array)) + copy(array, a.array) + } else { + array = a.array + } + return array +} + +// 清空数据数组 +func (a *Array) Clear() *Array { + 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() + return a +} + +// 查找指定数值是否存在 +func (a *Array) Contains(value interface{}) bool { + return a.Search(value) != -1 +} + +// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-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{})) *Array { + a.mu.Lock(true) + defer a.mu.Unlock(true) + f(a.array) + return a +} + +// 使用自定义方法执行加锁读取操作 +func (a *Array) RLockFunc(f func(array []interface{})) *Array { + a.mu.RLock(true) + defer a.mu.RUnlock(true) + f(a.array) + return a +} + +// 合并两个数组. +func (a *Array) Merge(array *Array) *Array { + a.mu.Lock() + defer a.mu.Unlock() + if a != array { + array.mu.RLock() + defer array.mu.RUnlock() + } + a.array = append(a.array, array.array...) + return a +} + +// Fills an array with num entries of the value of the value parameter, keys starting at the start_index 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 +} + +// Chunks an array into arrays with size elements. The last chunk may contain less than size elements. +func (a *Array) Chunk(size int) [][]interface{} { + if size < 1 { + panic("size: cannot be less than 1") + } + 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 array to the specified length with a value. +// If size is positive then the array is padded on the right, +// if it's negative then 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 +} + +// Extract a slice of the array(If in concurrent safe usage, it returns a copy of the slice; else a pointer). +// It returns the sequence of elements from the array array as specified by the offset and length parameters. +func (a *Array) SubSlice(offset, size int) []interface{} { + a.mu.RLock() + defer a.mu.RUnlock() + if offset > len(a.array) { + return nil + } + if offset + size > 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:] + } +} + +// Picks one or more random entries out of an array(a copy), and returns the key (or keys) of the random entries. +func (a *Array) Rand(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 +} + +// 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 +} + +// Make 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 array elements with a string. +func (a *Array) Join(glue string) string { + a.mu.RLock() + defer a.mu.RUnlock() + return strings.Join(gconv.Strings(a.array), glue) +} \ No newline at end of file diff --git a/g/container/garray/garray_normal_string.go b/g/container/garray/garray_normal_string.go new file mode 100644 index 000000000..920c02605 --- /dev/null +++ b/g/container/garray/garray_normal_string.go @@ -0,0 +1,394 @@ +// 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" + "gitee.com/johng/gf/g/util/grand" + "math" + "sort" + "strings" +) + +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 NewStringArrayFrom(array []string, unsafe...bool) *StringArray { + return &StringArray { + mu : rwmutex.New(unsafe...), + array : array, + } +} + +// 获取指定索引的数据项, 调用方注意判断数组边界 +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) *StringArray { + a.mu.Lock() + defer a.mu.Unlock() + a.array[index] = value + return a +} + +// 设置底层数组变量. +func (a *StringArray) SetArray(array []string) *StringArray { + a.mu.Lock() + defer a.mu.Unlock() + a.array = array + return a +} + +// 将数组重新排序(从小到大). +func (a *StringArray) Sort() *StringArray { + a.mu.Lock() + defer a.mu.Unlock() + sort.Strings(a.array) + return a +} + +// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界 +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 +} + +// 在当前索引位置后插入一个数据项, 调用方注意判断数组边界 +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 +} + +// 删除指定索引的数据项, 调用方注意判断数组边界 +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 +} + +// 将数据项添加到数组的最左端(索引为0) +func (a *StringArray) PushLeft(value...string) *StringArray { + a.mu.Lock() + a.array = append(value, a.array...) + a.mu.Unlock() + return a +} + +// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append +func (a *StringArray) PushRight(value...string) *StringArray { + a.mu.Lock() + a.array = append(a.array, value...) + a.mu.Unlock() + return a +} + +// 将最左端(索引为0)的数据项移出数组,并返回该数据项 +func (a *StringArray) PopLeft() string { + a.mu.Lock() + defer a.mu.Unlock() + value := a.array[0] + a.array = a.array[1 : ] + return value +} + +// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项 +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 +} + +// 随机将一个数据项移出数组,并返回该数据项 +func (a *StringArray) PopRand() string { + return a.Remove(grand.Intn(len(a.array))) +} + +// 追加数据项 +func (a *StringArray) Append(value...string) *StringArray { + a.mu.Lock() + a.array = append(a.array, value...) + a.mu.Unlock() + return a +} + +// 数组长度 +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() + defer a.mu.RUnlock() + array = make([]string, len(a.array)) + copy(array, a.array) + } else { + array = a.array + } + return array +} + +// 清空数据数组 +func (a *StringArray) Clear() *StringArray { + 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() + return a +} + +// 查找指定数值是否存在 +func (a *StringArray) Contains(value string) bool { + return a.Search(value) != -1 +} + +// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-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)) *StringArray { + a.mu.Lock(true) + defer a.mu.Unlock(true) + f(a.array) + return a +} + +// 使用自定义方法执行加锁读取操作 +func (a *StringArray) RLockFunc(f func(array []string)) *StringArray { + a.mu.RLock(true) + defer a.mu.RUnlock(true) + f(a.array) + return a +} + +// 合并两个数组. +func (a *StringArray) Merge(array *StringArray) *StringArray { + a.mu.Lock() + defer a.mu.Unlock() + if a != array { + array.mu.RLock() + defer array.mu.RUnlock() + } + a.array = append(a.array, array.array...) + return a +} + +// Fills an array with num entries of the value of the value parameter, keys starting at the start_index 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 +} + +// Chunks an array into arrays with size elements. The last chunk may contain less than size elements. +func (a *StringArray) Chunk(size int) [][]string { + if size < 1 { + panic("size: cannot be less than 1") + } + 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 array to the specified length with a value. +// If size is positive then the array is padded on the right, +// if it's negative then 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 +} + +// Extract a slice of the array(If in concurrent safe usage, it returns a copy of the slice; else a pointer). +// It returns the sequence of elements from the array array as specified by the offset and length parameters. +func (a *StringArray) SubSlice(offset, size int) []string { + a.mu.RLock() + defer a.mu.RUnlock() + if offset > len(a.array) { + return nil + } + if offset + size > 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:] + } +} + +// Picks one or more random entries out of an array(a copy), and returns the key (or keys) of the random entries. +func (a *StringArray) Rand(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 +} + +// 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 +} + +// Make 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 array elements with a string. +func (a *StringArray) Join(glue string) string { + a.mu.RLock() + defer a.mu.RUnlock() + return strings.Join(a.array, glue) +} + diff --git a/g/container/garray/garray_sorted_int.go b/g/container/garray/garray_sorted_int.go index 21082698f..d2c6ec0ee 100644 --- a/g/container/garray/garray_sorted_int.go +++ b/g/container/garray/garray_sorted_int.go @@ -9,9 +9,14 @@ package garray import ( "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/internal/rwmutex" + "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/util/grand" + "math" + "sort" + "strings" ) -// 默认按照从低到高进行排序 +// 默认按照从小到大进行排序 type SortedIntArray struct { mu *rwmutex.RWMutex // 互斥锁 cap int // 初始化设置的数组容量 @@ -38,10 +43,34 @@ func NewSortedIntArray(cap int, unsafe...bool) *SortedIntArray { } } +func NewSortedIntArrayFrom(array []int, unsafe...bool) *SortedIntArray { + a := NewSortedIntArray(0, unsafe...) + a.array = array + sort.Ints(a.array) + return a +} + +// 设置底层数组变量. +func (a *SortedIntArray) SetArray(array []int) *SortedIntArray { + a.mu.Lock() + defer a.mu.Unlock() + a.array = array + sort.Ints(a.array) + return a +} + +// 将数组重新排序(从小到大). +func (a *SortedIntArray) Sort() *SortedIntArray { + a.mu.Lock() + defer a.mu.Unlock() + sort.Ints(a.array) + return a +} + // 添加加数据项 -func (a *SortedIntArray) Add(values...int) { +func (a *SortedIntArray) Add(values...int) *SortedIntArray { if len(values) == 0 { - return + return a } a.mu.Lock() defer a.mu.Unlock() @@ -62,6 +91,7 @@ func (a *SortedIntArray) Add(values...int) { a.array = append(a.array[0 : index], value) a.array = append(a.array, rear...) } + return a } // 获取指定索引的数据项, 调用方注意判断数组边界 @@ -124,17 +154,21 @@ func (a *SortedIntArray) Slice() []int { array := ([]int)(nil) if a.mu.IsSafe() { a.mu.RLock() + defer a.mu.RUnlock() array = make([]int, len(a.array)) - for k, v := range a.array { - array[k] = v - } - a.mu.RUnlock() + copy(array, a.array) } else { array = a.array } return array } +// 查找指定数值是否存在 +func (a *SortedIntArray) Contains(value int) bool { + _, r := a.Search(value) + return r == 0 +} + // 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果 // 返回值: 最后比较位置, 比较结果 func (a *SortedIntArray) Search(value int) (index int, result int) { @@ -167,16 +201,17 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) } // 设置是否允许数组唯一 -func (a *SortedIntArray) SetUnique(unique bool) { +func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray { oldUnique := a.unique.Val() a.unique.Set(unique) if unique && oldUnique != unique { - a.doUnique() + a.Unique() } + return a } // 清理数组中重复的元素项 -func (a *SortedIntArray) doUnique() { +func (a *SortedIntArray) Unique() *SortedIntArray { a.mu.Lock() i := 0 for { @@ -190,27 +225,109 @@ func (a *SortedIntArray) doUnique() { } } a.mu.Unlock() + return a } // 清空数据数组 -func (a *SortedIntArray) Clear() { +func (a *SortedIntArray) Clear() *SortedIntArray { a.mu.Lock() if len(a.array) > 0 { a.array = make([]int, 0, a.cap) } a.mu.Unlock() + return a } // 使用自定义方法执行加锁修改操作 -func (a *SortedIntArray) LockFunc(f func(array []int)) { +func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray { a.mu.Lock(true) defer a.mu.Unlock(true) f(a.array) + return a } // 使用自定义方法执行加锁读取操作 -func (a *SortedIntArray) RLockFunc(f func(array []int)) { +func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray { a.mu.RLock(true) defer a.mu.RUnlock(true) f(a.array) + return a +} + +// 合并两个数组. +func (a *SortedIntArray) Merge(array *SortedIntArray) *SortedIntArray { + a.mu.Lock() + defer a.mu.Unlock() + if a != array { + array.mu.RLock() + defer array.mu.RUnlock() + } + a.array = append(a.array, array.array...) + sort.Ints(a.array) + return a +} + +// Chunks an array into arrays with size elements. The last chunk may contain less than size elements. +func (a *SortedIntArray) Chunk(size int) [][]int { + if size < 1 { + panic("size: cannot be less than 1") + } + 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 +} + +// Extract a slice of the array(If in concurrent safe usage, it returns a copy of the slice; else a pointer). +// It returns the sequence of elements from the array array as specified by the offset and length parameters. +func (a *SortedIntArray) SubSlice(offset, size int) []int { + a.mu.RLock() + defer a.mu.RUnlock() + if offset > len(a.array) { + return nil + } + if offset + size > 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:] + } +} + +// Picks one or more random entries out of an array(a copy), and returns the key (or keys) of the random entries. +func (a *SortedIntArray) Rand(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 array elements with a string. +func (a *SortedIntArray) Join(glue string) string { + a.mu.RLock() + defer a.mu.RUnlock() + return strings.Join(gconv.Strings(a.array), glue) } \ No newline at end of file diff --git a/g/container/garray/garray_sorted_interface.go b/g/container/garray/garray_sorted_interface.go index 27ab6cdcb..98efacd89 100644 --- a/g/container/garray/garray_sorted_interface.go +++ b/g/container/garray/garray_sorted_interface.go @@ -9,9 +9,14 @@ package garray import ( "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/internal/rwmutex" + "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/util/grand" + "math" + "sort" + "strings" ) -// 默认按照从低到高进行排序 +// 默认按照从小到大进行排序 type SortedArray struct { mu *rwmutex.RWMutex // 互斥锁 cap int // 初始化设置的数组容量 @@ -29,10 +34,40 @@ func NewSortedArray(cap int, compareFunc func(v1, v2 interface{}) int, unsafe... } } +func NewSortedArrayFrom(array []interface{}, compareFunc func(v1, v2 interface{}) int, unsafe...bool) *SortedArray { + a := NewSortedArray(0, compareFunc, unsafe...) + a.array = array + sort.Slice(a.array, func(i, j int) bool { + return a.compareFunc(a.array[i], a.array[j]) < 0 + }) + return a +} + +// 设置底层数组变量. +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.compareFunc(a.array[i], a.array[j]) < 0 + }) + return a +} + +// 将数组重新排序(从小到大). +func (a *SortedArray) Sort() *SortedArray { + a.mu.Lock() + defer a.mu.Unlock() + sort.Slice(a.array, func(i, j int) bool { + return a.compareFunc(a.array[i], a.array[j]) < 0 + }) + return a +} + // 添加加数据项 -func (a *SortedArray) Add(values...interface{}) { +func (a *SortedArray) Add(values...interface{}) *SortedArray { if len(values) == 0 { - return + return a } a.mu.Lock() defer a.mu.Unlock() @@ -53,6 +88,7 @@ func (a *SortedArray) Add(values...interface{}) { a.array = append(a.array[0 : index], value) a.array = append(a.array, rear...) } + return a } // 获取指定索引的数据项, 调用方注意判断数组边界 @@ -115,17 +151,21 @@ func (a *SortedArray) Slice() []interface{} { array := ([]interface{})(nil) if a.mu.IsSafe() { a.mu.RLock() + defer a.mu.RUnlock() array = make([]interface{}, len(a.array)) - for k, v := range a.array { - array[k] = v - } - a.mu.RUnlock() + copy(array, a.array) } else { array = a.array } return array } +// 查找指定数值是否存在 +func (a *SortedArray) Contains(value interface{}) bool { + _, r := a.Search(value) + return r == 0 +} + // 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果 // 返回值: 最后比较位置, 比较结果 func (a *SortedArray) Search(value interface{}) (index int, result int) { @@ -160,16 +200,17 @@ func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result } // 设置是否允许数组唯一 -func (a *SortedArray) SetUnique(unique bool) { +func (a *SortedArray) SetUnique(unique bool) *SortedArray { oldUnique := a.unique.Val() a.unique.Set(unique) if unique && oldUnique != unique { - a.doUnique() + a.Unique() } + return a } // 清理数组中重复的元素项 -func (a *SortedArray) doUnique() { +func (a *SortedArray) Unique() *SortedArray { a.mu.Lock() defer a.mu.Unlock() i := 0 @@ -183,27 +224,111 @@ func (a *SortedArray) doUnique() { i++ } } + return a } // 清空数据数组 -func (a *SortedArray) Clear() { +func (a *SortedArray) Clear() *SortedArray { a.mu.Lock() if len(a.array) > 0 { a.array = make([]interface{}, 0, a.cap) } a.mu.Unlock() + return a } // 使用自定义方法执行加锁修改操作 -func (a *SortedArray) LockFunc(f func(array []interface{})) { +func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray { a.mu.Lock(true) defer a.mu.Unlock(true) f(a.array) + return a } // 使用自定义方法执行加锁读取操作 -func (a *SortedArray) RLockFunc(f func(array []interface{})) { +func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray { a.mu.RLock(true) defer a.mu.RUnlock(true) f(a.array) + return a +} + +// 合并两个数组. +func (a *SortedArray) Merge(array *SortedArray) *SortedArray { + a.mu.Lock() + defer a.mu.Unlock() + if a != array { + array.mu.RLock() + defer array.mu.RUnlock() + } + a.array = append(a.array, array.array...) + sort.Slice(a.array, func(i, j int) bool { + return a.compareFunc(a.array[i], a.array[j]) < 0 + }) + return a +} + +// Chunks an array into arrays with size elements. The last chunk may contain less than size elements. +func (a *SortedArray) Chunk(size int) [][]interface{} { + if size < 1 { + panic("size: cannot be less than 1") + } + 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 +} + +// Extract a slice of the array(If in concurrent safe usage, it returns a copy of the slice; else a pointer). +// It returns the sequence of elements from the array array as specified by the offset and length parameters. +func (a *SortedArray) SubSlice(offset, size int) []interface{} { + a.mu.RLock() + defer a.mu.RUnlock() + if offset > len(a.array) { + return nil + } + if offset + size > 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:] + } +} + +// Picks one or more random entries out of an array(a copy), and returns the key (or keys) of the random entries. +func (a *SortedArray) Rand(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 array elements with a string. +func (a *SortedArray) Join(glue string) string { + a.mu.RLock() + defer a.mu.RUnlock() + return strings.Join(gconv.Strings(a.array), glue) } \ No newline at end of file diff --git a/g/container/garray/garray_sorted_string.go b/g/container/garray/garray_sorted_string.go index 7cae7e73d..bdb7e5517 100644 --- a/g/container/garray/garray_sorted_string.go +++ b/g/container/garray/garray_sorted_string.go @@ -8,11 +8,14 @@ package garray import ( "gitee.com/johng/gf/g/container/gtype" - "strings" "gitee.com/johng/gf/g/internal/rwmutex" + "gitee.com/johng/gf/g/util/grand" + "math" + "sort" + "strings" ) -// 默认按照从低到高进行排序 +// 默认按照从小到大进行排序 type SortedStringArray struct { mu *rwmutex.RWMutex // 互斥锁 cap int // 初始化设置的数组容量 @@ -32,10 +35,34 @@ func NewSortedStringArray(cap int, unsafe...bool) *SortedStringArray { } } +func NewSortedStringArrayFrom(array []string, unsafe...bool) *SortedStringArray { + a := NewSortedStringArray(0, unsafe...) + a.array = array + sort.Strings(a.array) + return a +} + +// 设置底层数组变量. +func (a *SortedStringArray) SetArray(array []string) *SortedStringArray { + a.mu.Lock() + defer a.mu.Unlock() + a.array = array + sort.Strings(a.array) + return a +} + +// 将数组重新排序(从小到大). +func (a *SortedStringArray) Sort() *SortedStringArray { + a.mu.Lock() + defer a.mu.Unlock() + sort.Strings(a.array) + return a +} + // 添加加数据项 -func (a *SortedStringArray) Add(values...string) { +func (a *SortedStringArray) Add(values...string) *SortedStringArray { if len(values) == 0 { - return + return a } a.mu.Lock() defer a.mu.Unlock() @@ -56,6 +83,7 @@ func (a *SortedStringArray) Add(values...string) { a.array = append(a.array[0 : index], value) a.array = append(a.array, rear...) } + return a } // 获取指定索引的数据项, 调用方注意判断数组边界 @@ -118,17 +146,21 @@ func (a *SortedStringArray) Slice() []string { array := ([]string)(nil) if a.mu.IsSafe() { a.mu.RLock() + defer a.mu.RUnlock() array = make([]string, len(a.array)) - for k, v := range a.array { - array[k] = v - } - a.mu.RUnlock() + copy(array, a.array) } else { array = a.array } return array } +// 查找指定数值是否存在 +func (a *SortedStringArray) Contains(value string) bool { + _, r := a.Search(value) + return r == 0 +} + // 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果 // 返回值: 最后比较位置, 比较结果 func (a *SortedStringArray) Search(value string) (index int, result int) { @@ -161,16 +193,17 @@ func (a *SortedStringArray) binSearch(value string, lock bool) (index int, resul } // 设置是否允许数组唯一 -func (a *SortedStringArray) SetUnique(unique bool) { +func (a *SortedStringArray) SetUnique(unique bool) *SortedStringArray { oldUnique := a.unique.Val() a.unique.Set(unique) if unique && oldUnique != unique { - a.doUnique() + a.Unique() } + return a } // 清理数组中重复的元素项 -func (a *SortedStringArray) doUnique() { +func (a *SortedStringArray) Unique() *SortedStringArray { a.mu.Lock() i := 0 for { @@ -184,27 +217,109 @@ func (a *SortedStringArray) doUnique() { } } a.mu.Unlock() + return a } // 清空数据数组 -func (a *SortedStringArray) Clear() { +func (a *SortedStringArray) Clear() *SortedStringArray { a.mu.Lock() if len(a.array) > 0 { a.array = make([]string, 0, a.cap) } a.mu.Unlock() + return a } // 使用自定义方法执行加锁修改操作 -func (a *SortedStringArray) LockFunc(f func(array []string)) { +func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray { a.mu.Lock(true) defer a.mu.Unlock(true) f(a.array) + return a } // 使用自定义方法执行加锁读取操作 -func (a *SortedStringArray) RLockFunc(f func(array []string)) { +func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray { a.mu.RLock(true) defer a.mu.RUnlock(true) f(a.array) + return a +} + +// 合并两个数组. +func (a *SortedStringArray) Merge(array *SortedStringArray) *SortedStringArray { + a.mu.Lock() + defer a.mu.Unlock() + if a != array { + array.mu.RLock() + defer array.mu.RUnlock() + } + a.array = append(a.array, array.array...) + sort.Strings(a.array) + return a +} + +// Chunks an array into arrays with size elements. The last chunk may contain less than size elements. +func (a *SortedStringArray) Chunk(size int) [][]string { + if size < 1 { + panic("size: cannot be less than 1") + } + 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 +} + +// Extract a slice of the array(If in concurrent safe usage, it returns a copy of the slice; else a pointer). +// It returns the sequence of elements from the array array as specified by the offset and length parameters. +func (a *SortedStringArray) SubSlice(offset, size int) []string { + a.mu.RLock() + defer a.mu.RUnlock() + if offset > len(a.array) { + return nil + } + if offset + size > 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:] + } +} + +// Picks one or more random entries out of an array(a copy), and returns the key (or keys) of the random entries. +func (a *SortedStringArray) Rand(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 array elements with a string. +func (a *SortedStringArray) Join(glue string) string { + a.mu.RLock() + defer a.mu.RUnlock() + return strings.Join(a.array, glue) } \ No newline at end of file diff --git a/g/container/garray/garray_string.go b/g/container/garray/garray_string.go deleted file mode 100644 index b6298a207..000000000 --- a/g/container/garray/garray_string.go +++ /dev/null @@ -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) -} diff --git a/g/container/garray/garray_z_unit_test.go b/g/container/garray/garray_z_unit_basic_test.go similarity index 98% rename from g/container/garray/garray_z_unit_test.go rename to g/container/garray/garray_z_unit_basic_test.go index ddbed30a2..23c07b631 100644 --- a/g/container/garray/garray_z_unit_test.go +++ b/g/container/garray/garray_z_unit_basic_test.go @@ -10,13 +10,12 @@ package garray_test import ( "gitee.com/johng/gf/g/container/garray" + "gitee.com/johng/gf/g/test/gtest" "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) diff --git a/g/container/garray/garray_z_unit_int_test.go b/g/container/garray/garray_z_unit_int_test.go new file mode 100644 index 000000000..63c11ea22 --- /dev/null +++ b/g/container/garray/garray_z_unit_int_test.go @@ -0,0 +1,161 @@ +// 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/test/gtest" + "testing" +) + +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() { + expect := []int{0, 1, 2, 3} + array := garray.NewIntArray(0, 0) + for i := 3; i >= 0; i-- { + array.Append(i) + } + array.Sort() + gtest.Assert(array.Slice(), expect) + }) +} + +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_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(0, 2), []int{0,1}) + gtest.Assert(array1.SubSlice(2, 2), []int{2,3}) + gtest.Assert(array1.SubSlice(5, 8), []int{5,6}) + }) +} + +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.Rand(2)), 2) + gtest.Assert(len(array1.Rand(10)), 7) + gtest.AssertIN(array1.Rand(1)[0], a1) + }) +} + +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") + }) +} \ No newline at end of file diff --git a/g/container/glist/glist.go b/g/container/glist/glist.go index d27843091..0659047f1 100644 --- a/g/container/glist/glist.go +++ b/g/container/glist/glist.go @@ -7,7 +7,7 @@ // Package glist provides a concurrent-safe(alternative) doubly linked list. // -// 并发安全的双向链表. +// 并发安全双向链表. package glist import ( diff --git a/g/container/gmap/gmap.go b/g/container/gmap/gmap.go index e149f35ec..b12f88f96 100644 --- a/g/container/gmap/gmap.go +++ b/g/container/gmap/gmap.go @@ -6,7 +6,7 @@ // Package gmap provides kinds of concurrent-safe(alternative) maps. // -// 并发安全的哈希MAP. +// 并发安全MAP. package gmap // 默认的Map对象其实就是InterfaceInterfaceMap的别名。 diff --git a/g/container/gmap/gmap_func.go b/g/container/gmap/gmap_func.go new file mode 100644 index 000000000..15ebd4650 --- /dev/null +++ b/g/container/gmap/gmap_func.go @@ -0,0 +1,8 @@ +// Copyright 2019 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 + diff --git a/g/container/gmap/gmap_int_bool_map.go b/g/container/gmap/gmap_int_bool_map.go index 199e22480..bda848ca4 100644 --- a/g/container/gmap/gmap_int_bool_map.go +++ b/g/container/gmap/gmap_int_bool_map.go @@ -220,4 +220,17 @@ func (this *IntBoolMap) RLockFunc(f func(m map[int]bool)) { this.mu.RLock(true) defer this.mu.RUnlock(true) f(this.m) +} + +// 合并两个Map. +func (this *IntBoolMap) Merge(m *IntBoolMap) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } } \ No newline at end of file diff --git a/g/container/gmap/gmap_int_int_map.go b/g/container/gmap/gmap_int_int_map.go index 94f042f67..4cfeb6a5e 100644 --- a/g/container/gmap/gmap_int_int_map.go +++ b/g/container/gmap/gmap_int_int_map.go @@ -221,3 +221,27 @@ func (this *IntIntMap) RLockFunc(f func(m map[int]int)) { defer this.mu.RUnlock(true) f(this.m) } + +// 交换Map中的键和值. +func (this *IntIntMap) Flip() { + this.mu.Lock() + defer this.mu.Unlock() + n := make(map[int]int, len(this.m)) + for k, v := range this.m { + n[v] = k + } + this.m = n +} + +// 合并两个Map. +func (this *IntIntMap) Merge(m *IntIntMap) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } +} diff --git a/g/container/gmap/gmap_int_interface_map.go b/g/container/gmap/gmap_int_interface_map.go index dd147e958..e6d0a5bb1 100644 --- a/g/container/gmap/gmap_int_interface_map.go +++ b/g/container/gmap/gmap_int_interface_map.go @@ -7,7 +7,10 @@ package gmap -import "gitee.com/johng/gf/g/internal/rwmutex" +import ( + "gitee.com/johng/gf/g/internal/rwmutex" + "gitee.com/johng/gf/g/util/gconv" +) type IntInterfaceMap struct { mu *rwmutex.RWMutex @@ -204,3 +207,27 @@ func (this *IntInterfaceMap) RLockFunc(f func(m map[int]interface{})) { defer this.mu.RUnlock(true) f(this.m) } + +// 交换Map中的键和值. +func (this *IntInterfaceMap) Flip() { + this.mu.Lock() + defer this.mu.Unlock() + n := make(map[int]interface{}, len(this.m)) + for k, v := range this.m { + n[gconv.Int(v)] = k + } + this.m = n +} + +// 合并两个Map. +func (this *IntInterfaceMap) Merge(m *IntInterfaceMap) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } +} \ No newline at end of file diff --git a/g/container/gmap/gmap_int_string_map.go b/g/container/gmap/gmap_int_string_map.go index 0411a3032..9f6cc4cf0 100644 --- a/g/container/gmap/gmap_int_string_map.go +++ b/g/container/gmap/gmap_int_string_map.go @@ -9,6 +9,7 @@ package gmap import ( "gitee.com/johng/gf/g/internal/rwmutex" + "gitee.com/johng/gf/g/util/gconv" ) type IntStringMap struct { @@ -221,3 +222,27 @@ func (this *IntStringMap) RLockFunc(f func(m map[int]string)) { defer this.mu.RUnlock(true) f(this.m) } + +// 交换Map中的键和值. +func (this *IntStringMap) Flip() { + this.mu.Lock() + defer this.mu.Unlock() + n := make(map[int]string, len(this.m)) + for k, v := range this.m { + n[gconv.Int(v)] = gconv.String(k) + } + this.m = n +} + +// 合并两个Map. +func (this *IntStringMap) Merge(m *IntStringMap) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } +} \ No newline at end of file diff --git a/g/container/gmap/gmap_interface_interface_map.go b/g/container/gmap/gmap_interface_interface_map.go index c9ed697c7..725f28f4b 100644 --- a/g/container/gmap/gmap_interface_interface_map.go +++ b/g/container/gmap/gmap_interface_interface_map.go @@ -206,3 +206,27 @@ func (this *InterfaceInterfaceMap) RLockFunc(f func(m map[interface{}]interface{ defer this.mu.RUnlock(true) f(this.m) } + +// 交换Map中的键和值. +func (this *InterfaceInterfaceMap) Flip() { + this.mu.Lock() + defer this.mu.Unlock() + n := make(map[interface{}]interface{}, len(this.m)) + for i, v := range this.m { + n[v] = i + } + this.m = n +} + +// 合并两个Map. +func (this *InterfaceInterfaceMap) Merge(m *Map) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } +} diff --git a/g/container/gmap/gmap_string_bool_map.go b/g/container/gmap/gmap_string_bool_map.go index 2f07f0250..80c9707c8 100644 --- a/g/container/gmap/gmap_string_bool_map.go +++ b/g/container/gmap/gmap_string_bool_map.go @@ -221,3 +221,16 @@ func (this *StringBoolMap) RLockFunc(f func(m map[string]bool)) { defer this.mu.RUnlock(true) f(this.m) } + +// 合并两个Map. +func (this *StringBoolMap) Merge(m *StringBoolMap) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } +} \ No newline at end of file diff --git a/g/container/gmap/gmap_string_int_map.go b/g/container/gmap/gmap_string_int_map.go index 939f853f6..77a805e68 100644 --- a/g/container/gmap/gmap_string_int_map.go +++ b/g/container/gmap/gmap_string_int_map.go @@ -7,7 +7,10 @@ package gmap -import "gitee.com/johng/gf/g/internal/rwmutex" +import ( + "gitee.com/johng/gf/g/internal/rwmutex" + "gitee.com/johng/gf/g/util/gconv" +) type StringIntMap struct { mu *rwmutex.RWMutex @@ -219,3 +222,27 @@ func (this *StringIntMap) RLockFunc(f func(m map[string]int)) { defer this.mu.RUnlock(true) f(this.m) } + +// 交换Map中的键和值. +func (this *StringIntMap) Flip() { + this.mu.Lock() + defer this.mu.Unlock() + n := make(map[string]int, len(this.m)) + for k, v := range this.m { + n[gconv.String(v)] = gconv.Int(k) + } + this.m = n +} + +// 合并两个Map. +func (this *StringIntMap) Merge(m *StringIntMap) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } +} \ No newline at end of file diff --git a/g/container/gmap/gmap_string_interface_map.go b/g/container/gmap/gmap_string_interface_map.go index b7ab9a5a9..bc033013f 100644 --- a/g/container/gmap/gmap_string_interface_map.go +++ b/g/container/gmap/gmap_string_interface_map.go @@ -9,6 +9,7 @@ package gmap import ( "gitee.com/johng/gf/g/internal/rwmutex" + "gitee.com/johng/gf/g/util/gconv" ) type StringInterfaceMap struct { @@ -206,3 +207,27 @@ func (this *StringInterfaceMap) RLockFunc(f func(m map[string]interface{})) { defer this.mu.RUnlock(true) f(this.m) } + +// 交换Map中的键和值. +func (this *StringInterfaceMap) Flip() { + this.mu.Lock() + defer this.mu.Unlock() + n := make(map[string]interface{}, len(this.m)) + for k, v := range this.m { + n[gconv.String(v)] = k + } + this.m = n +} + +// 合并两个Map. +func (this *StringInterfaceMap) Merge(m *StringInterfaceMap) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } +} \ No newline at end of file diff --git a/g/container/gmap/gmap_string_string_map.go b/g/container/gmap/gmap_string_string_map.go index fc18893e6..c303d5dff 100644 --- a/g/container/gmap/gmap_string_string_map.go +++ b/g/container/gmap/gmap_string_string_map.go @@ -7,7 +7,9 @@ package gmap -import "gitee.com/johng/gf/g/internal/rwmutex" +import ( + "gitee.com/johng/gf/g/internal/rwmutex" +) type StringStringMap struct { mu *rwmutex.RWMutex @@ -219,3 +221,27 @@ func (this *StringStringMap) RLockFunc(f func(m map[string]string)) { defer this.mu.RUnlock(true) f(this.m) } + +// 交换Map中的键和值. +func (this *StringStringMap) Flip() { + this.mu.Lock() + defer this.mu.Unlock() + n := make(map[string]string, len(this.m)) + for k, v := range this.m { + n[v] = k + } + this.m = n +} + +// 合并两个Map. +func (this *StringStringMap) Merge(m *StringStringMap) { + this.mu.Lock() + defer this.mu.Unlock() + if m != this { + m.mu.RLock() + defer m.mu.RUnlock() + } + for k, v := range m.m { + this.m[k] = v + } +} diff --git a/g/container/gmap/gmap_bench_safe_test.go b/g/container/gmap/gmap_z_bench_safe_test.go similarity index 100% rename from g/container/gmap/gmap_bench_safe_test.go rename to g/container/gmap/gmap_z_bench_safe_test.go diff --git a/g/container/gmap/gmap_bench_syncmap_test.go b/g/container/gmap/gmap_z_bench_syncmap_test.go similarity index 100% rename from g/container/gmap/gmap_bench_syncmap_test.go rename to g/container/gmap/gmap_z_bench_syncmap_test.go diff --git a/g/container/gmap/gmap_bench_unsafe_test.go b/g/container/gmap/gmap_z_bench_unsafe_test.go similarity index 100% rename from g/container/gmap/gmap_bench_unsafe_test.go rename to g/container/gmap/gmap_z_bench_unsafe_test.go diff --git a/g/container/gqueue/gqueue.go b/g/container/gqueue/gqueue.go index 257ce8235..dbb4128e5 100644 --- a/g/container/gqueue/gqueue.go +++ b/g/container/gqueue/gqueue.go @@ -6,7 +6,7 @@ // Package gqueue provides a dynamic/static concurrent-safe(alternative) queue. // -// 并发安全的动态队列. +// 并发安全动态队列. // // 特点: // 1. 动态队列初始化速度快; diff --git a/g/container/gset/gset_int_set.go b/g/container/gset/gset_int_set.go index c254369f2..44501b904 100644 --- a/g/container/gset/gset_int_set.go +++ b/g/container/gset/gset_int_set.go @@ -26,7 +26,7 @@ func NewIntSet(unsafe...bool) *IntSet { } // 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历 -func (set *IntSet) Iterator(f func (v int) bool) { +func (set *IntSet) Iterator(f func (v int) bool) *IntSet { set.mu.RLock() defer set.mu.RUnlock() for k, _ := range set.m { @@ -34,6 +34,7 @@ func (set *IntSet) Iterator(f func (v int) bool) { break } } + return set } // 设置键 @@ -62,11 +63,12 @@ func (set *IntSet) Contains(item int) bool { return exists } -// 删除键值对 -func (set *IntSet) Remove(key int) { +// 删除元素项 +func (set *IntSet) Remove(key int) *IntSet { set.mu.Lock() delete(set.m, key) set.mu.Unlock() + return set } // 大小 @@ -78,10 +80,11 @@ func (set *IntSet) Size() int { } // 清空set -func (set *IntSet) Clear() { +func (set *IntSet) Clear() *IntSet { set.mu.Lock() set.m = make(map[int]struct{}) set.mu.Unlock() + return set } // 转换为数组 @@ -93,7 +96,6 @@ func (set *IntSet) Slice() []int { ret[i] = item i++ } - set.mu.RUnlock() return ret } @@ -103,14 +105,127 @@ func (set *IntSet) String() string { return fmt.Sprint(set.Slice()) } -func (set *IntSet) LockFunc(f func(m map[int]struct{})) { +func (set *IntSet) LockFunc(f func(m map[int]struct{})) *IntSet { set.mu.Lock(true) defer set.mu.Unlock(true) f(set.m) + return set } -func (set *IntSet) RLockFunc(f func(m map[int]struct{})) { +func (set *IntSet) RLockFunc(f func(m map[int]struct{})) *IntSet { set.mu.RLock(true) defer set.mu.RUnlock(true) f(set.m) + return set +} + +// 判断两个集合是否相等. +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 +} + +// 判断other集合是否为当前集合的子集. +func (set *IntSet) IsSubset(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 other.m { + if _, ok := set.m[key]; !ok { + return false + } + } + return true +} + +// 并集, 返回新的集合:属于set或属于other的元素为元素的集合. +func (set *IntSet) Union(other *IntSet) (newSet *IntSet) { + newSet = NewIntSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + if set != other { + other.mu.RLock() + defer other.mu.RUnlock() + } + for k, v := range set.m { + newSet.m[k] = v + } + if set != other { + for k, v := range other.m { + newSet.m[k] = v + } + } + return +} + +// 差集, 返回新的集合: 属于set且不属于other的元素为元素的集合. +func (set *IntSet) Diff(other *IntSet) (newSet *IntSet) { + newSet = NewIntSet(true) + if set == other { + return newSet + } + set.mu.RLock() + defer set.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + + for k, v := range set.m { + if _, ok := other.m[k]; !ok { + newSet.m[k] = v + } + } + return +} + +// 交集, 返回新的集合: 属于set且属于other的元素为元素的集合. +func (set *IntSet) Inter(other *IntSet) (newSet *IntSet) { + newSet = NewIntSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + if set != other { + other.mu.RLock() + defer other.mu.RUnlock() + } + for k, v := range set.m { + if _, ok := other.m[k]; ok { + newSet.m[k] = v + } + } + return +} + +// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合. +func (set *IntSet) Complement(full *IntSet) (newSet *IntSet) { + newSet = NewIntSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + full.mu.RLock() + defer full.mu.RUnlock() + for k, v := range full.m { + if _, ok := set.m[k]; !ok { + newSet.m[k] = v + } + } + return } \ No newline at end of file diff --git a/g/container/gset/gset_interface_set.go b/g/container/gset/gset_interface_set.go index 0950898f8..20089e29c 100644 --- a/g/container/gset/gset_interface_set.go +++ b/g/container/gset/gset_interface_set.go @@ -25,7 +25,7 @@ func NewInterfaceSet(unsafe...bool) *InterfaceSet { } // 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历 -func (set *InterfaceSet) Iterator(f func (v interface{}) bool) { +func (set *InterfaceSet) Iterator(f func (v interface{}) bool) *InterfaceSet { set.mu.RLock() defer set.mu.RUnlock() for k, _ := range set.m { @@ -33,6 +33,7 @@ func (set *InterfaceSet) Iterator(f func (v interface{}) bool) { break } } + return set } // 添加 @@ -62,10 +63,11 @@ func (set *InterfaceSet) Contains(item interface{}) bool { } // 删除键值对 -func (set *InterfaceSet) Remove(key interface{}) { +func (set *InterfaceSet) Remove(key interface{}) *InterfaceSet { set.mu.Lock() delete(set.m, key) set.mu.Unlock() + return set } // 大小 @@ -77,10 +79,11 @@ func (set *InterfaceSet) Size() int { } // 清空set -func (set *InterfaceSet) Clear() { +func (set *InterfaceSet) Clear() *InterfaceSet { set.mu.Lock() set.m = make(map[interface{}]struct{}) set.mu.Unlock() + return set } // 转换为数组 @@ -101,14 +104,127 @@ func (set *InterfaceSet) String() string { return fmt.Sprint(set.Slice()) } -func (set *InterfaceSet) LockFunc(f func(m map[interface{}]struct{})) { +func (set *InterfaceSet) LockFunc(f func(m map[interface{}]struct{})) *InterfaceSet { set.mu.Lock(true) defer set.mu.Unlock(true) f(set.m) + return set } -func (set *InterfaceSet) RLockFunc(f func(m map[interface{}]struct{})) { +func (set *InterfaceSet) RLockFunc(f func(m map[interface{}]struct{})) *InterfaceSet { set.mu.RLock(true) defer set.mu.RUnlock(true) f(set.m) + return set } + +// 判断两个集合是否相等. +func (set *InterfaceSet) Equal(other *InterfaceSet) 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 +} + +// 判断other集合是否为当前集合的子集. +func (set *InterfaceSet) IsSubset(other *InterfaceSet) 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 other.m { + if _, ok := set.m[key]; !ok { + return false + } + } + return true +} + +// 并集, 返回新的集合:属于set或属于other的元素为元素的集合. +func (set *InterfaceSet) Union(other *InterfaceSet) (newSet *InterfaceSet) { + newSet = NewInterfaceSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + if set != other { + other.mu.RLock() + defer other.mu.RUnlock() + } + for k, v := range set.m { + newSet.m[k] = v + } + if set != other { + for k, v := range other.m { + newSet.m[k] = v + } + } + return +} + +// 差集, 返回新的集合: 属于set且不属于other的元素为元素的集合. +func (set *InterfaceSet) Diff(other *InterfaceSet) (newSet *InterfaceSet) { + newSet = NewInterfaceSet(true) + if set == other { + return newSet + } + set.mu.RLock() + defer set.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + + for k, v := range set.m { + if _, ok := other.m[k]; !ok { + newSet.m[k] = v + } + } + return +} + +// 交集, 返回新的集合: 属于set且属于other的元素为元素的集合. +func (set *InterfaceSet) Inter(other *InterfaceSet) (newSet *InterfaceSet) { + newSet = NewInterfaceSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + if set != other { + other.mu.RLock() + defer other.mu.RUnlock() + } + for k, v := range set.m { + if _, ok := other.m[k]; ok { + newSet.m[k] = v + } + } + return +} + +// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合. +func (set *InterfaceSet) Complement(full *InterfaceSet) (newSet *InterfaceSet) { + newSet = NewInterfaceSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + full.mu.RLock() + defer full.mu.RUnlock() + for k, v := range full.m { + if _, ok := set.m[k]; !ok { + newSet.m[k] = v + } + } + return +} \ No newline at end of file diff --git a/g/container/gset/gset_string_set.go b/g/container/gset/gset_string_set.go index 6aa7cde7f..329e475e5 100644 --- a/g/container/gset/gset_string_set.go +++ b/g/container/gset/gset_string_set.go @@ -25,7 +25,7 @@ func NewStringSet(unsafe...bool) *StringSet { } // 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历 -func (set *StringSet) Iterator(f func (v string) bool) { +func (set *StringSet) Iterator(f func (v string) bool) *StringSet { set.mu.RLock() defer set.mu.RUnlock() for k, _ := range set.m { @@ -33,6 +33,7 @@ func (set *StringSet) Iterator(f func (v string) bool) { break } } + return set } // 设置键 @@ -62,10 +63,11 @@ func (set *StringSet) Contains(item string) bool { } // 删除键值对 -func (set *StringSet) Remove(key string) { +func (set *StringSet) Remove(key string) *StringSet { set.mu.Lock() delete(set.m, key) set.mu.Unlock() + return set } // 大小 @@ -77,10 +79,11 @@ func (set *StringSet) Size() int { } // 清空set -func (set *StringSet) Clear() { +func (set *StringSet) Clear() *StringSet { set.mu.Lock() set.m = make(map[string]struct{}) set.mu.Unlock() + return set } // 转换为数组 @@ -102,14 +105,127 @@ func (set *StringSet) String() string { return fmt.Sprint(set.Slice()) } -func (set *StringSet) LockFunc(f func(m map[string]struct{})) { +func (set *StringSet) LockFunc(f func(m map[string]struct{})) *StringSet { set.mu.Lock(true) defer set.mu.Unlock(true) f(set.m) + return set } -func (set *StringSet) RLockFunc(f func(m map[string]struct{})) { +func (set *StringSet) RLockFunc(f func(m map[string]struct{})) *StringSet { set.mu.RLock(true) defer set.mu.RUnlock(true) f(set.m) + return set } + +// 判断两个集合是否相等. +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 +} + +// 判断other集合是否为当前集合的子集. +func (set *StringSet) IsSubset(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 other.m { + if _, ok := set.m[key]; !ok { + return false + } + } + return true +} + +// 并集, 返回新的集合:属于set或属于other的元素为元素的集合. +func (set *StringSet) Union(other *StringSet) (newSet *StringSet) { + newSet = NewStringSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + if set != other { + other.mu.RLock() + defer other.mu.RUnlock() + } + for k, v := range set.m { + newSet.m[k] = v + } + if set != other { + for k, v := range other.m { + newSet.m[k] = v + } + } + return +} + +// 差集, 返回新的集合: 属于set且不属于other的元素为元素的集合. +func (set *StringSet) Diff(other *StringSet) (newSet *StringSet) { + newSet = NewStringSet(true) + if set == other { + return newSet + } + set.mu.RLock() + defer set.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + + for k, v := range set.m { + if _, ok := other.m[k]; !ok { + newSet.m[k] = v + } + } + return +} + +// 交集, 返回新的集合: 属于set且属于other的元素为元素的集合. +func (set *StringSet) Inter(other *StringSet) (newSet *StringSet) { + newSet = NewStringSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + if set != other { + other.mu.RLock() + defer other.mu.RUnlock() + } + for k, v := range set.m { + if _, ok := other.m[k]; ok { + newSet.m[k] = v + } + } + return +} + +// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合. +func (set *StringSet) Complement(full *StringSet) (newSet *StringSet) { + newSet = NewStringSet(true) + set.mu.RLock() + defer set.mu.RUnlock() + full.mu.RLock() + defer full.mu.RUnlock() + for k, v := range full.m { + if _, ok := set.m[k]; !ok { + newSet.m[k] = v + } + } + return +} \ No newline at end of file diff --git a/g/container/gset/gset_test.go b/g/container/gset/gset_z_bench_test.go similarity index 100% rename from g/container/gset/gset_test.go rename to g/container/gset/gset_z_bench_test.go diff --git a/g/container/gset/gset_unsafe_test.go b/g/container/gset/gset_z_bench_unsafe_test.go similarity index 100% rename from g/container/gset/gset_unsafe_test.go rename to g/container/gset/gset_z_bench_unsafe_test.go diff --git a/g/container/gset/gset_z_unit_test.go b/g/container/gset/gset_z_unit_test.go new file mode 100644 index 000000000..c6ea1eb8f --- /dev/null +++ b/g/container/gset/gset_z_unit_test.go @@ -0,0 +1,25 @@ +// 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 gset_test + +import ( + "gitee.com/johng/gf/g/container/gset" + "gitee.com/johng/gf/g/test/gtest" + "testing" +) + +func TestIntSet_Basic(t *testing.T) { + gtest.Case(t, func() { + s := gset.NewIntSet() + s.Add(1).Add(1).Add(2) + s.BatchAdd([]int{3,4}) + gtest.Assert(s.Size(), 3) + gtest.Assert(s.Contains(4), true) + }) +} diff --git a/g/crypto/gdes/gdes.go b/g/crypto/gdes/gdes.go index e60645c4f..b50289342 100644 --- a/g/crypto/gdes/gdes.go +++ b/g/crypto/gdes/gdes.go @@ -262,14 +262,14 @@ func PKCS5Unpadding(text []byte) []byte{ //补位方法 func Padding(text []byte, padding int)([]byte, error) { switch padding { - case NOPADDING: - if len(text) % 8 != 0 { - return nil, errors.New("text length invalid") - } - case PKCS5PADDING: - return PKCS5Padding(text, 8), nil - default: - return nil, errors.New("padding type error") + case NOPADDING: + if len(text) % 8 != 0 { + return nil, errors.New("text length invalid") + } + case PKCS5PADDING: + return PKCS5Padding(text, 8), nil + default: + return nil, errors.New("padding type error") } return text, nil @@ -278,14 +278,14 @@ func Padding(text []byte, padding int)([]byte, error) { //去除补位方法 func UnPadding(text []byte, padding int)([]byte, error) { switch padding { - case NOPADDING: - if len(text) % 8 != 0 { - return nil, errors.New("text length invalid") - } - case PKCS5PADDING: - return PKCS5Unpadding(text), nil - default: - return nil, errors.New("padding type error.") + case NOPADDING: + if len(text) % 8 != 0 { + return nil, errors.New("text length invalid") + } + case PKCS5PADDING: + return PKCS5Unpadding(text), nil + default: + return nil, errors.New("padding type error.") } return text, nil } \ No newline at end of file diff --git a/g/crypto/gsha1/gsha1.go b/g/crypto/gsha1/gsha1.go index 4dc3cbde1..b233fdb77 100644 --- a/g/crypto/gsha1/gsha1.go +++ b/g/crypto/gsha1/gsha1.go @@ -34,6 +34,7 @@ func EncryptFile(path string) string { if e != nil { return "" } + defer f.Close() h := sha1.New() _, e = io.Copy(h, f) if e != nil { diff --git a/g/database/gdb/gdb_base.go b/g/database/gdb/gdb_base.go index f5d78e19c..641755fcd 100644 --- a/g/database/gdb/gdb_base.go +++ b/g/database/gdb/gdb_base.go @@ -15,7 +15,7 @@ import ( "gitee.com/johng/gf/g/os/gcache" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "reflect" "strings" ) diff --git a/g/database/gdb/gdb_func.go b/g/database/gdb/gdb_func.go index 9a200b50a..16638e549 100644 --- a/g/database/gdb/gdb_func.go +++ b/g/database/gdb/gdb_func.go @@ -13,8 +13,8 @@ import ( "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gregex" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gregex" + "gitee.com/johng/gf/g/string/gstr" _ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql" "reflect" "strings" diff --git a/g/database/gdb/gdb_mssql.go b/g/database/gdb/gdb_mssql.go index 70187a585..5c6e732d2 100644 --- a/g/database/gdb/gdb_mssql.go +++ b/g/database/gdb/gdb_mssql.go @@ -16,7 +16,7 @@ package gdb import ( "database/sql" "fmt" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "strconv" "strings" ) diff --git a/g/database/gdb/gdb_oracle.go b/g/database/gdb/gdb_oracle.go index d6d9873ec..2a5312c7c 100644 --- a/g/database/gdb/gdb_oracle.go +++ b/g/database/gdb/gdb_oracle.go @@ -16,7 +16,7 @@ package gdb import ( "database/sql" "fmt" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "strconv" "strings" ) diff --git a/g/database/gdb/gdb_structure.go b/g/database/gdb/gdb_structure.go index d5c9356b7..265b213cf 100644 --- a/g/database/gdb/gdb_structure.go +++ b/g/database/gdb/gdb_structure.go @@ -10,7 +10,7 @@ package gdb import ( "fmt" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "strings" ) diff --git a/g/database/gdb/gdb_transaction.go b/g/database/gdb/gdb_transaction.go index a1d28cfe4..b7d9ef7cc 100644 --- a/g/database/gdb/gdb_transaction.go +++ b/g/database/gdb/gdb_transaction.go @@ -8,7 +8,7 @@ package gdb import ( "database/sql" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" _ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql" ) diff --git a/g/database/gdb/gdb_unit_0_test.go b/g/database/gdb/gdb_unit_0_test.go index 017a6633f..878250091 100644 --- a/g/database/gdb/gdb_unit_0_test.go +++ b/g/database/gdb/gdb_unit_0_test.go @@ -2,7 +2,7 @@ package gdb_test import ( "gitee.com/johng/gf/g/database/gdb" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" ) var ( diff --git a/g/database/gdb/gdb_unit_1_test.go b/g/database/gdb/gdb_unit_1_test.go index 8e3bd979f..88f99e12e 100644 --- a/g/database/gdb/gdb_unit_1_test.go +++ b/g/database/gdb/gdb_unit_1_test.go @@ -3,7 +3,7 @@ package gdb_test import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" ) diff --git a/g/database/gdb/gdb_unit_2_test.go b/g/database/gdb/gdb_unit_2_test.go index c63f3626b..d8ad54be9 100644 --- a/g/database/gdb/gdb_unit_2_test.go +++ b/g/database/gdb/gdb_unit_2_test.go @@ -3,7 +3,7 @@ package gdb_test import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" ) diff --git a/g/database/gdb/gdb_unit_3_test.go b/g/database/gdb/gdb_unit_3_test.go index 3180c906d..4526163e0 100644 --- a/g/database/gdb/gdb_unit_3_test.go +++ b/g/database/gdb/gdb_unit_3_test.go @@ -3,7 +3,7 @@ package gdb_test import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" ) diff --git a/g/encoding/gjson/gjson.go b/g/encoding/gjson/gjson.go index dabb85548..b2fb5e7e1 100644 --- a/g/encoding/gjson/gjson.go +++ b/g/encoding/gjson/gjson.go @@ -9,7 +9,7 @@ package gjson import ( "errors" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "strings" "strconv" "io/ioutil" @@ -19,7 +19,7 @@ import ( "gitee.com/johng/gf/g/encoding/gxml" "gitee.com/johng/gf/g/encoding/gyaml" "gitee.com/johng/gf/g/encoding/gtoml" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" "time" "gitee.com/johng/gf/g/internal/rwmutex" "fmt" diff --git a/g/encoding/gurl/url.go b/g/encoding/gurl/url.go index 65bf2eea4..929c4b97a 100644 --- a/g/encoding/gurl/url.go +++ b/g/encoding/gurl/url.go @@ -7,7 +7,10 @@ // Package gurl provides useful API for URL handling. package gurl -import "net/url" +import ( + "net/url" + "strings" +) // url encode string, is + not %20 func Encode(str string) string { @@ -18,3 +21,60 @@ func Encode(str string) string { func Decode(str string) (string, error) { return url.QueryUnescape(str) } + +// URL-encode according to RFC 3986. +// See http://php.net/manual/en/function.rawurlencode.php. +func RawEncode(str string) string { + return strings.Replace(url.QueryEscape(str), "+", "%20", -1) +} + +// Decode URL-encoded strings. +// See http://php.net/manual/en/function.rawurldecode.php. +func RawDecode(str string) (string, error) { + return url.QueryUnescape(strings.Replace(str, "%20", "+", -1)) +} + +// Generate URL-encoded query string. +// See http://php.net/manual/en/function.http-build-query.php. +func BuildQuery(queryData url.Values) string { + return queryData.Encode() +} + +// Parse a URL and return its components. +// -1: all; 1: scheme; 2: host; 4: port; 8: user; 16: pass; 32: path; 64: query; 128: fragment. +// See http://php.net/manual/en/function.parse-url.php. +func ParseURL(str string, component int) (map[string]string, error) { + u, err := url.Parse(str) + if err != nil { + return nil, err + } + if component == -1 { + component = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 + } + var components = make(map[string]string) + if (component & 1) == 1 { + components["scheme"] = u.Scheme + } + if (component & 2) == 2 { + components["host"] = u.Hostname() + } + if (component & 4) == 4 { + components["port"] = u.Port() + } + if (component & 8) == 8 { + components["user"] = u.User.Username() + } + if (component & 16) == 16 { + components["pass"], _ = u.User.Password() + } + if (component & 32) == 32 { + components["path"] = u.Path + } + if (component & 64) == 64 { + components["query"] = u.RawQuery + } + if (component & 128) == 128 { + components["fragment"] = u.Fragment + } + return components, nil +} diff --git a/g/encoding/gxml/gxml.go b/g/encoding/gxml/gxml.go index 351aa838f..39d547206 100644 --- a/g/encoding/gxml/gxml.go +++ b/g/encoding/gxml/gxml.go @@ -11,7 +11,7 @@ import ( "gitee.com/johng/gf/third/github.com/clbanning/mxj" "encoding/xml" "io" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "gitee.com/johng/gf/third/github.com/axgle/mahonia" "errors" "fmt" diff --git a/g/frame/gins/gins.go b/g/frame/gins/gins.go index 45247785b..6c9b275fb 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -22,7 +22,7 @@ import ( "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gview" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" ) const ( diff --git a/g/net/ghttp/ghttp_client_request_client.go b/g/net/ghttp/ghttp_client_request_client.go index 410392fdd..607cf75ed 100644 --- a/g/net/ghttp/ghttp_client_request_client.go +++ b/g/net/ghttp/ghttp_client_request_client.go @@ -9,7 +9,7 @@ package ghttp import ( - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "time" "bytes" "strings" diff --git a/g/net/ghttp/ghttp_request.go b/g/net/ghttp/ghttp_request.go index 2ce2368e4..552890ac1 100644 --- a/g/net/ghttp/ghttp_request.go +++ b/g/net/ghttp/ghttp_request.go @@ -10,7 +10,7 @@ import ( "gitee.com/johng/gf/g/container/gvar" "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "gitee.com/johng/gf/third/github.com/fatih/structs" "io/ioutil" "net/http" diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 311fefe9d..6b6938e22 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -20,7 +20,7 @@ import ( "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/os/gtimer" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "gitee.com/johng/gf/third/github.com/gorilla/websocket" "gitee.com/johng/gf/third/github.com/olekukonko/tablewriter" "net/http" diff --git a/g/net/ghttp/ghttp_server_router.go b/g/net/ghttp/ghttp_server_router.go index 5e3cc5e9b..b2405b9ce 100644 --- a/g/net/ghttp/ghttp_server_router.go +++ b/g/net/ghttp/ghttp_server_router.go @@ -12,8 +12,8 @@ import ( "errors" "fmt" "gitee.com/johng/gf/g/os/glog" - "gitee.com/johng/gf/g/util/gregex" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gregex" + "gitee.com/johng/gf/g/string/gstr" "runtime" "strings" ) diff --git a/g/net/ghttp/ghttp_server_router_hook.go b/g/net/ghttp/ghttp_server_router_hook.go index dac1cd2d3..bf9d333a3 100644 --- a/g/net/ghttp/ghttp_server_router_hook.go +++ b/g/net/ghttp/ghttp_server_router_hook.go @@ -11,7 +11,7 @@ import ( "container/list" "fmt" "gitee.com/johng/gf/g/container/gset" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "reflect" "runtime" "strings" diff --git a/g/net/ghttp/ghttp_server_router_serve.go b/g/net/ghttp/ghttp_server_router_serve.go index 4e746a952..2445aab2d 100644 --- a/g/net/ghttp/ghttp_server_router_serve.go +++ b/g/net/ghttp/ghttp_server_router_serve.go @@ -10,7 +10,7 @@ package ghttp import ( "strings" "container/list" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" ) // 查询请求处理方法. diff --git a/g/net/ghttp/ghttp_server_service_controller.go b/g/net/ghttp/ghttp_server_service_controller.go index e20fddb94..5e6ceedb8 100644 --- a/g/net/ghttp/ghttp_server_service_controller.go +++ b/g/net/ghttp/ghttp_server_service_controller.go @@ -14,7 +14,7 @@ import ( "reflect" "fmt" "gitee.com/johng/gf/g/os/gfile" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" ) // 绑定控制器,控制器需要实现gmvc.Controller接口 diff --git a/g/net/ghttp/ghttp_server_service_handler.go b/g/net/ghttp/ghttp_server_service_handler.go index 2fd1ff3f5..c45b8beb9 100644 --- a/g/net/ghttp/ghttp_server_service_handler.go +++ b/g/net/ghttp/ghttp_server_service_handler.go @@ -10,7 +10,7 @@ package ghttp import ( "errors" "strings" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" "bytes" "runtime" "reflect" diff --git a/g/net/ghttp/ghttp_server_service_object.go b/g/net/ghttp/ghttp_server_service_object.go index ef64df4a1..ce6eb6451 100644 --- a/g/net/ghttp/ghttp_server_service_object.go +++ b/g/net/ghttp/ghttp_server_service_object.go @@ -13,7 +13,7 @@ import ( "strings" "reflect" "fmt" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" "gitee.com/johng/gf/g/os/gfile" ) diff --git a/g/net/ghttp/ghttp_unit_1_test.go b/g/net/ghttp/ghttp_unit_1_test.go index e8251e717..de6b66a05 100644 --- a/g/net/ghttp/ghttp_unit_1_test.go +++ b/g/net/ghttp/ghttp_unit_1_test.go @@ -11,7 +11,7 @@ import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/net/ghttp/ghttp_unit_2_test.go b/g/net/ghttp/ghttp_unit_2_test.go index 76afbca89..eafdfee04 100644 --- a/g/net/ghttp/ghttp_unit_2_test.go +++ b/g/net/ghttp/ghttp_unit_2_test.go @@ -12,7 +12,7 @@ import ( "gitee.com/johng/gf/g/frame/gmvc" "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/net/ghttp/ghttp_unit_3_test.go b/g/net/ghttp/ghttp_unit_3_test.go index 3543c5d97..3ad93d7b3 100644 --- a/g/net/ghttp/ghttp_unit_3_test.go +++ b/g/net/ghttp/ghttp_unit_3_test.go @@ -11,7 +11,7 @@ import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/net/gipv4/gipv4.go b/g/net/gipv4/gipv4.go index 4aeeb70e1..302502f6b 100644 --- a/g/net/gipv4/gipv4.go +++ b/g/net/gipv4/gipv4.go @@ -9,12 +9,13 @@ package gipv4 import ( + "encoding/binary" "net" "strconv" "strings" "regexp" "fmt" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" ) // 判断所给地址是否是一个IPv4地址 @@ -22,33 +23,58 @@ func Validate(ip string) bool { return gregex.IsMatchString(`^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$`, ip) } -// ip字符串转为整形 -func Ip2long(ipstr string) (ip uint32) { - reg, _ := regexp.Compile(`^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$`) - ips := reg.FindStringSubmatch(ipstr) - if ips == nil { - return +// Get the IPv4 address corresponding to a given Internet host name. +func GetHostByName(hostname string) (string, error) { + ips, err := net.LookupIP(hostname) + if ips != nil { + for _, v := range ips { + if v.To4() != nil { + return v.String(), nil + } + } + return "", nil } + return "", err +} - ip1, _ := strconv.Atoi(ips[1]) - ip2, _ := strconv.Atoi(ips[2]) - ip3, _ := strconv.Atoi(ips[3]) - ip4, _ := strconv.Atoi(ips[4]) - - if ip1>255 || ip2>255 || ip3>255 || ip4 > 255 { - return +// Get a list of IPv4 addresses corresponding to a given Internet host name. +func GetHostsByName(hostname string) ([]string, error) { + ips, err := net.LookupIP(hostname) + if ips != nil { + var ipStrs []string + for _, v := range ips { + if v.To4() != nil { + ipStrs = append(ipStrs, v.String()) + } + } + return ipStrs, nil } + return nil, err +} - ip += uint32(ip1 * 0x1000000) - ip += uint32(ip2 * 0x10000) - ip += uint32(ip3 * 0x100) - ip += uint32(ip4) - return +// Get the Internet host name corresponding to a given IP address. +func GetNameByAddr(ipAddress string) (string, error) { + names, err := net.LookupAddr(ipAddress) + if names != nil { + return strings.TrimRight(names[0], "."), nil + } + return "", err +} + +// IP字符串转为整形. +func Ip2long(ipAddress string) uint32 { + ip := net.ParseIP(ipAddress) + if ip == nil { + return 0 + } + return binary.BigEndian.Uint32(ip.To4()) } // ip整形转为字符串 -func Long2ip(ip uint32) string { - return fmt.Sprintf("%d.%d.%d.%d", ip>>24, ip<<8>>24, ip<<16>>24, ip<<24>>24) +func Long2ip(properAddress uint32) string { + ipByte := make([]byte, 4) + binary.BigEndian.PutUint32(ipByte, properAddress) + return net.IP(ipByte).String() } // 获得ip的网段,例如:192.168.2.102 -> 192.168.2 diff --git a/g/net/gipv6/gipv6.go b/g/net/gipv6/gipv6.go index 58bab6af3..055f5e027 100644 --- a/g/net/gipv6/gipv6.go +++ b/g/net/gipv6/gipv6.go @@ -7,7 +7,7 @@ // Package gipv4 provides useful API for IPv6 address handling. package gipv6 -import "gitee.com/johng/gf/g/util/gregex" +import "gitee.com/johng/gf/g/string/gregex" // 判断所给地址是否是一个IPv6地址 func Validate(ip string) bool { diff --git a/g/os/gcache/gcache_z_unit_1_test.go b/g/os/gcache/gcache_z_unit_1_test.go index 74de36619..f978a2939 100644 --- a/g/os/gcache/gcache_z_unit_1_test.go +++ b/g/os/gcache/gcache_z_unit_1_test.go @@ -10,7 +10,7 @@ package gcache_test import ( "gitee.com/johng/gf/g/os/gcache" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/os/gcron/gcron_schedule.go b/g/os/gcron/gcron_schedule.go index a9f3abc54..35abe2b64 100644 --- a/g/os/gcron/gcron_schedule.go +++ b/g/os/gcron/gcron_schedule.go @@ -9,7 +9,7 @@ package gcron import ( "errors" "fmt" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "strconv" "strings" "time" diff --git a/g/os/gcron/gcron_unit_1_test.go b/g/os/gcron/gcron_unit_1_test.go index 5191ba604..8a43e5aa9 100644 --- a/g/os/gcron/gcron_unit_1_test.go +++ b/g/os/gcron/gcron_unit_1_test.go @@ -10,7 +10,7 @@ package gcron_test import ( "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/os/gcron" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/os/gcron/gcron_unit_2_test.go b/g/os/gcron/gcron_unit_2_test.go index 0bdef20ad..01d5f07ff 100644 --- a/g/os/gcron/gcron_unit_2_test.go +++ b/g/os/gcron/gcron_unit_2_test.go @@ -11,7 +11,7 @@ import ( "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/os/gcron" "gitee.com/johng/gf/g/os/glog" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index e98354552..fba5dd3a6 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -15,8 +15,8 @@ import ( "fmt" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gregex" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gregex" + "gitee.com/johng/gf/g/string/gstr" "io" "os" "os/exec" @@ -202,7 +202,7 @@ func Remove(path string) error { return os.RemoveAll(path) } -// 文件是否可读 +// 文件是否可读(支持文件/目录) func IsReadable(path string) bool { result := true file, err := os.OpenFile(path, os.O_RDONLY, gDEFAULT_PERM) @@ -213,17 +213,17 @@ func IsReadable(path string) bool { return result } -// 文件是否可写 +// 文件是否可写(支持文件/目录) func IsWritable(path string) bool { result := true if IsDir(path) { // 如果是目录,那么创建一个临时文件进行写入测试 - tfile := strings.TrimRight(path, Separator) + Separator + gconv.String(time.Now().UnixNano()) - err := Create(tfile) - if err != nil || !Exists(tfile){ + tmpFile := strings.TrimRight(path, Separator) + Separator + gconv.String(time.Now().UnixNano()) + err := Create(tmpFile) + if err != nil || !Exists(tmpFile){ result = false } else { - Remove(tfile) + Remove(tmpFile) } } else { // 如果是文件,那么判断文件是否可打开 @@ -421,4 +421,4 @@ func MainPkgPath() string { // 系统临时目录 func TempDir() string { return os.TempDir() -} \ No newline at end of file +} diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index 2d8e4fe37..2e273b3c5 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -14,7 +14,7 @@ import ( "gitee.com/johng/gf/g/os/gfpool" "gitee.com/johng/gf/g/os/gmlock" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "io" "os" "runtime" diff --git a/g/os/gmlock/gmlock_unit_lock_test.go b/g/os/gmlock/gmlock_unit_lock_test.go index ced3a201a..553061de7 100644 --- a/g/os/gmlock/gmlock_unit_lock_test.go +++ b/g/os/gmlock/gmlock_unit_lock_test.go @@ -9,7 +9,7 @@ package gmlock_test import ( "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/os/gmlock" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/os/gmlock/gmlock_unit_rlock_test.go b/g/os/gmlock/gmlock_unit_rlock_test.go index ffd210868..1d8bfe905 100644 --- a/g/os/gmlock/gmlock_unit_rlock_test.go +++ b/g/os/gmlock/gmlock_unit_rlock_test.go @@ -9,7 +9,7 @@ package gmlock_test import ( "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/os/gmlock" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/os/gspath/gspath.go b/g/os/gspath/gspath.go index da5ffff01..fa228acfe 100644 --- a/g/os/gspath/gspath.go +++ b/g/os/gspath/gspath.go @@ -17,7 +17,7 @@ import ( "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/gfsnotify" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" "runtime" "sort" "strings" diff --git a/g/os/gtime/gtime.go b/g/os/gtime/gtime.go index 4b7cef60e..5d6df5486 100644 --- a/g/os/gtime/gtime.go +++ b/g/os/gtime/gtime.go @@ -11,8 +11,8 @@ package gtime import ( "errors" - "gitee.com/johng/gf/g/util/gregex" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gregex" + "gitee.com/johng/gf/g/string/gstr" "regexp" "strconv" "strings" diff --git a/g/os/gtime/gtime_format.go b/g/os/gtime/gtime_format.go index a0c9a9366..4be19ca0f 100644 --- a/g/os/gtime/gtime_format.go +++ b/g/os/gtime/gtime_format.go @@ -8,7 +8,7 @@ package gtime import ( "bytes" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "strings" ) diff --git a/g/os/gtimer/gtimer_z_unit_0_test.go b/g/os/gtimer/gtimer_z_unit_0_test.go index 09aa7c0f8..d7db1db2e 100644 --- a/g/os/gtimer/gtimer_z_unit_0_test.go +++ b/g/os/gtimer/gtimer_z_unit_0_test.go @@ -11,7 +11,7 @@ package gtimer_test import ( "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/os/gtimer" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/os/gtimer/gtimer_z_unit_1_test.go b/g/os/gtimer/gtimer_z_unit_1_test.go index 15779a4cd..e992ba22a 100644 --- a/g/os/gtimer/gtimer_z_unit_1_test.go +++ b/g/os/gtimer/gtimer_z_unit_1_test.go @@ -11,7 +11,7 @@ package gtimer_test import ( "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/os/gtimer" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/os/gtimer/gtimer_z_unit_2_test.go b/g/os/gtimer/gtimer_z_unit_2_test.go index 41a9393bb..f625f6c40 100644 --- a/g/os/gtimer/gtimer_z_unit_2_test.go +++ b/g/os/gtimer/gtimer_z_unit_2_test.go @@ -11,7 +11,7 @@ package gtimer_test import ( "gitee.com/johng/gf/g/container/garray" "gitee.com/johng/gf/g/os/gtimer" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go index 0b379cc13..8affbb46c 100644 --- a/g/os/gview/gview.go +++ b/g/os/gview/gview.go @@ -25,7 +25,7 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/os/gview/internal/text/template" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" "strings" "sync" ) diff --git a/g/util/gregex/gregex.go b/g/string/gregex/gregex.go similarity index 100% rename from g/util/gregex/gregex.go rename to g/string/gregex/gregex.go diff --git a/g/util/gregex/gregex_test.go b/g/string/gregex/gregex_z_bench_test.go similarity index 100% rename from g/util/gregex/gregex_test.go rename to g/string/gregex/gregex_z_bench_test.go diff --git a/g/string/gstr/gstr.go b/g/string/gstr/gstr.go new file mode 100644 index 000000000..432b2a202 --- /dev/null +++ b/g/string/gstr/gstr.go @@ -0,0 +1,576 @@ +// 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 gstr provides useful API for string handling. +// +// 字符串操作. +package gstr + +import ( + "bytes" + "fmt" + "gitee.com/johng/gf/g/util/grand" + "math" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// 字符串替换(大小写敏感) +func Replace(origin, search, replace string, count...int) string { + n := -1 + if len(count) > 0 { + n = count[0] + } + return strings.Replace(origin, search, replace, n) +} + +// 使用map进行字符串替换(大小写敏感) +func ReplaceByMap(origin string, replaces map[string]string) string { + result := origin + for k, v := range replaces { + result = strings.Replace(result, k, v, -1) + } + return result +} + +// 字符串转换为小写 +func ToLower(s string) string { + return strings.ToLower(s) +} + +// 字符串转换为大写 +func ToUpper(s string) string { + return strings.ToUpper(s) +} + +// 字符串首字母转换为大写 +func UcFirst(s string) string { + if len(s) == 0 { + return s + } + if IsLetterLower(s[0]) { + return string(s[0] - 32) + s[1 :] + } + return s +} + +// 字符串首字母转换为小写 +func LcFirst(s string) string { + if len(s) == 0 { + return s + } + if IsLetterUpper(s[0]) { + return string(s[0] + 32) + s[1 :] + } + return s +} + +// Uppercase the first character of each word in a string. +func UcWords(str string) string { + return strings.Title(str) +} + +// 便利数组查找字符串索引位置,如果不存在则返回-1,使用完整遍历查找 +func SearchArray (a []string, s string) int { + for i, v := range a { + if s == v { + return i + } + } + return -1 +} + +// 判断字符串是否在数组中 +func InArray (a []string, s string) bool { + return SearchArray(a, s) != -1 +} + +// 判断给定字符是否小写 +func IsLetterLower(b byte) bool { + if b >= byte('a') && b <= byte('z') { + return true + } + return false +} + +// 判断给定字符是否大写 +func IsLetterUpper(b byte) bool { + if b >= byte('A') && b <= byte('Z') { + return true + } + return false +} + +// 判断锁给字符串是否为数字 +func IsNumeric(s string) bool { + length := len(s) + if length == 0 { + return false + } + for i := 0; i < len(s); i++ { + if s[i] < byte('0') || s[i] > byte('9') { + return false + } + } + return true +} + +// 字符串截取,支持中文 +func SubStr(str string, start int, length...int) (substr string) { + // 将字符串的转换成[]rune + rs := []rune(str) + lth := len(rs) + // 简单的越界判断 + if start < 0 { + start = 0 + } + if start >= lth { + start = lth + } + end := lth + if len(length) > 0 { + end = start + length[0] + if end < start { + end = lth + } + } + if end > lth { + end = lth + } + // 返回子串 + return string(rs[start : end]) +} + +// 字符串长度截取限制,超过长度限制被截取并在字符串末尾追加指定的内容,支持中文 +func StrLimit(str string, length int, suffix...string) (string) { + rs := []rune(str) + if len(str) < length { + return str + } + addStr := "..." + if len(suffix) > 0 { + addStr = suffix[0] + } + return string(rs[0 : length]) + addStr +} + +// Reverse a string. +func Reverse(str string) string { + runes := []rune(str) + for i, j := 0, len(runes) - 1; i < j; i, j = i + 1, j - 1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// Format a number with grouped thousands. +// decimals: Sets the number of decimal points. +// decPoint: Sets the separator for the decimal point. +// thousandsSep: Sets the thousands separator. +func NumberFormat(number float64, decimals int, decPoint, thousandsSep string) string { + neg := false + if number < 0 { + number = -number + neg = true + } + // Will round off + str := fmt.Sprintf("%."+strconv.Itoa(decimals)+"F", number) + prefix, suffix := "", "" + if decimals > 0 { + prefix = str[ : len(str) - (decimals + 1)] + suffix = str[len(str) - decimals : ] + } else { + prefix = str + } + sep := []byte(thousandsSep) + n, l1, l2 := 0, len(prefix), len(sep) + // thousands sep num + c := (l1 - 1) / 3 + tmp := make([]byte, l2*c+l1) + pos := len(tmp) - 1 + for i := l1 - 1; i >= 0; i, n, pos = i-1, n+1, pos-1 { + if l2 > 0 && n > 0 && n%3 == 0 { + for j := range sep { + tmp[pos] = sep[l2-j-1] + pos-- + } + } + tmp[pos] = prefix[i] + } + s := string(tmp) + if decimals > 0 { + s += decPoint + suffix + } + if neg { + s = "-" + s + } + + return s +} + +// Split a string into smaller chunks. +func ChunkSplit(body string, chunkLen uint, end string) string { + if end == "" { + end = "\r\n" + } + runes, endRunes := []rune(body), []rune(end) + l := uint(len(runes)) + if l <= 1 || l < chunkLen { + return body + end + } + ns := make([]rune, 0, len(runes) + len(endRunes)) + var i uint + for i = 0; i < l; i += chunkLen { + if i + chunkLen > l { + ns = append(ns, runes[i : ]...) + } else { + ns = append(ns, runes[i : i + chunkLen]...) + } + ns = append(ns, endRunes...) + } + return string(ns) +} + +// Return information about words used in a string. +func WordCount(str string) []string { + return strings.Fields(str) +} + +// Wraps a string to a given number of characters. +func WordWrap(str string, width uint, br string) string { + if br == "" { + br = "\n" + } + init := make([]byte, 0, len(str)) + buf := bytes.NewBuffer(init) + var current uint + var wordBuf, spaceBuf bytes.Buffer + for _, char := range str { + if char == '\n' { + if wordBuf.Len() == 0 { + if current+uint(spaceBuf.Len()) > width { + current = 0 + } else { + current += uint(spaceBuf.Len()) + spaceBuf.WriteTo(buf) + } + spaceBuf.Reset() + } else { + current += uint(spaceBuf.Len() + wordBuf.Len()) + spaceBuf.WriteTo(buf) + spaceBuf.Reset() + wordBuf.WriteTo(buf) + wordBuf.Reset() + } + buf.WriteRune(char) + current = 0 + } else if unicode.IsSpace(char) { + if spaceBuf.Len() == 0 || wordBuf.Len() > 0 { + current += uint(spaceBuf.Len() + wordBuf.Len()) + spaceBuf.WriteTo(buf) + spaceBuf.Reset() + wordBuf.WriteTo(buf) + wordBuf.Reset() + } + spaceBuf.WriteRune(char) + } else { + wordBuf.WriteRune(char) + if current+uint(spaceBuf.Len()+wordBuf.Len()) > width && uint(wordBuf.Len()) < width { + buf.WriteString(br) + current = 0 + spaceBuf.Reset() + } + } + } + + if wordBuf.Len() == 0 { + if current+uint(spaceBuf.Len()) <= width { + spaceBuf.WriteTo(buf) + } + } else { + spaceBuf.WriteTo(buf) + wordBuf.WriteTo(buf) + } + return buf.String() +} + +// Get string length of unicode. +func RuneLen(str string) int { + return utf8.RuneCountInString(str) +} + +// Repeat a string. +func Repeat(input string, multiplier int) string { + return strings.Repeat(input, multiplier) +} + +// Returns part of haystack string starting from and including the first occurrence of needle to the end of haystack. +// See http://php.net/manual/en/function.strstr.php. +func Str(haystack string, needle string) string { + if needle == "" { + return "" + } + idx := strings.Index(haystack, needle) + if idx == -1 { + return "" + } + return haystack[idx + len([]byte(needle)) - 1 : ] +} + +// Translate characters or replace substrings. +// +// If the params length is 1, type is: map[string]string +// Tr("baab", map[string]string{"ab": "01"}) will return "ba01". +// If the params length is 2, type is: string, string +// Tr("baab", "ab", "01") will return "1001", a => 0; b => 1. +func Tr(haystack string, params ...interface{}) string { + ac := len(params) + if ac == 1 { + pairs := params[0].(map[string]string) + length := len(pairs) + if length == 0 { + return haystack + } + oldnew := make([]string, length*2) + for o, n := range pairs { + if o == "" { + return haystack + } + oldnew = append(oldnew, o, n) + } + return strings.NewReplacer(oldnew...).Replace(haystack) + } else if ac == 2 { + from := params[0].(string) + to := params[1].(string) + trlen, lt := len(from), len(to) + if trlen > lt { + trlen = lt + } + if trlen == 0 { + return haystack + } + + str := make([]uint8, len(haystack)) + var xlat [256]uint8 + var i int + var j uint8 + if trlen == 1 { + for i = 0; i < len(haystack); i++ { + if haystack[i] == from[0] { + str[i] = to[0] + } else { + str[i] = haystack[i] + } + } + return string(str) + } + // trlen != 1 + for { + xlat[j] = j + if j++; j == 0 { + break + } + } + for i = 0; i < trlen; i++ { + xlat[from[i]] = to[i] + } + for i = 0; i < len(haystack); i++ { + str[i] = xlat[haystack[i]] + } + return string(str) + } + + return haystack +} + +// Randomly shuffles a string. +func Shuffle(str string) string { + runes := []rune(str) + s := make([]rune, len(runes)) + for i, v := range grand.Perm(len(runes)) { + s[i] = runes[v] + } + return string(s) +} + +// Strip whitespace (or other characters) from the beginning and end of a string. +func Trim(str string, characterMask ...string) string { + mask := "" + if len(characterMask) == 0 { + mask = " \\t\\n\\r\\0\\x0B" + } else { + mask = characterMask[0] + } + return strings.Trim(str, mask) +} + +// Strip whitespace (or other characters) from the beginning of a string. +func TrimLeft(str string, characterMask ...string) string { + mask := "" + if len(characterMask) == 0 { + mask = " \\t\\n\\r\\0\\x0B" + } else { + mask = characterMask[0] + } + return strings.TrimLeft(str, mask) +} + +// Strip whitespace (or other characters) from the end of a string. +func TrimRight(str string, characterMask ...string) string { + mask := "" + if len(characterMask) == 0 { + mask = " \\t\\n\\r\\0\\x0B" + } else { + mask = characterMask[0] + } + return strings.TrimRight(str, mask) +} + +// Split a string by a string. +func Split(str, delimiter string) []string { + return strings.Split(str, delimiter) +} + +// Join concatenates the elements of a to create a single string. The separator string +// sep is placed between elements in the resulting string. +func Join(array []string, sep string) string { + return strings.Join(array, sep) +} + +// Split a string by a string. +func Explode(delimiter, str string) []string { + return Split(str, delimiter) +} + +// Join array elements with a string. +func Implode(glue string, pieces []string) string { + var buf bytes.Buffer + l := len(pieces) + for _, str := range pieces { + buf.WriteString(str) + if l--; l > 0 { + buf.WriteString(glue) + } + } + return buf.String() +} + +// Generate a single-byte string from a number. +func Chr(ascii int) string { + return string(ascii) +} + +// Convert the first byte of a string to a value between 0 and 255. +func Ord(char string) int { + r, _ := utf8.DecodeRune([]byte(char)) + return int(r) +} + +// 按照百分比从字符串中间向两边隐藏字符(主要用于姓名、手机号、邮箱地址、身份证号等的隐藏),支持utf-8中文,支持email格式。 +func HideStr(str string, percent int, hide string) string { + array := strings.Split(str, "@") + if len(array) > 1 { + str = array[0] + } + rs := []rune(str) + length := len(rs) + mid := math.Floor(float64(length/2)) + hideLen := int(math.Floor(float64(length) * (float64(percent)/100))) + start := int(mid - math.Floor(float64(hideLen) / 2)) + hideStr := []rune("") + hideRune := []rune(hide) + for i := 0; i < int(hideLen); i++ { + hideStr = append(hideStr, hideRune...) + } + buffer := bytes.NewBuffer(nil) + buffer.WriteString(string(rs[0 : start])) + buffer.WriteString(string(hideStr)) + buffer.WriteString(string(rs[start + hideLen : ])) + if len(array) > 1 { + buffer.WriteString(array[1]) + } + return buffer.String() +} + +// Inserts HTML line breaks before all newlines in a string. +// \n\r, \r\n, \r, \n +func Nl2Br(str string, isXhtml...bool) string { + r, n, runes := '\r', '\n', []rune(str) + var br []byte + if len(isXhtml) > 0 && isXhtml[0] { + br = []byte("
") + } else { + br = []byte("
") + } + skip := false + length := len(runes) + var buf bytes.Buffer + for i, v := range runes { + if skip { + skip = false + continue + } + switch v { + case n, r: + if (i+1 < length) && (v == r && runes[i+1] == n) || (v == n && runes[i+1] == r) { + buf.Write(br) + skip = true + continue + } + buf.Write(br) + default: + buf.WriteRune(v) + } + } + return buf.String() +} + +// Quote string with slashes. +func AddSlashes(str string) string { + var buf bytes.Buffer + for _, char := range str { + switch char { + case '\'', '"', '\\': + buf.WriteRune('\\') + } + buf.WriteRune(char) + } + return buf.String() +} + +// Un-quotes a quoted string. +func StripSlashes(str string) string { + var buf bytes.Buffer + l, skip := len(str), false + for i, char := range str { + if skip { + skip = false + } else if char == '\\' { + if i+1 < l && str[i+1] == '\\' { + skip = true + } + continue + } + buf.WriteRune(char) + } + return buf.String() +} + +// Returns a version of str with a backslash character (\) before every character that is among: +// .\+*?[^]($) +func QuoteMeta(str string) string { + var buf bytes.Buffer + for _, char := range str { + switch char { + case '.', '+', '\\', '(', '$', ')', '[', '^', ']', '*', '?': + buf.WriteRune('\\') + } + buf.WriteRune(char) + } + return buf.String() +} \ No newline at end of file diff --git a/g/string/gstr/gstr_levenshtein.go b/g/string/gstr/gstr_levenshtein.go new file mode 100644 index 000000000..10e6cc39c --- /dev/null +++ b/g/string/gstr/gstr_levenshtein.go @@ -0,0 +1,55 @@ +package gstr + +// Calculate Levenshtein distance between two strings. +// costIns: Defines the cost of insertion. +// costRep: Defines the cost of replacement. +// costDel: Defines the cost of deletion. +// See http://php.net/manual/en/function.levenshtein.php. +func Levenshtein(str1, str2 string, costIns, costRep, costDel int) int { + var maxLen = 255 + l1 := len(str1) + l2 := len(str2) + if l1 == 0 { + return l2 * costIns + } + if l2 == 0 { + return l1 * costDel + } + if l1 > maxLen || l2 > maxLen { + return -1 + } + + tmp := make([]int, l2+1) + p1 := make([]int, l2+1) + p2 := make([]int, l2+1) + var c0, c1, c2 int + var i1, i2 int + for i2 := 0; i2 <= l2; i2++ { + p1[i2] = i2 * costIns + } + for i1 = 0; i1 < l1; i1++ { + p2[0] = p1[0] + costDel + for i2 = 0; i2 < l2; i2++ { + if str1[i1] == str2[i2] { + c0 = p1[i2] + } else { + c0 = p1[i2] + costRep + } + c1 = p1[i2+1] + costDel + if c1 < c0 { + c0 = c1 + } + c2 = p2[i2] + costIns + if c2 < c0 { + c0 = c2 + } + p2[i2+1] = c0 + } + tmp = p1 + p1 = p2 + p2 = tmp + } + c0 = p1[l2] + + return c0 +} \ No newline at end of file diff --git a/g/string/gstr/gstr_parse.go b/g/string/gstr/gstr_parse.go new file mode 100644 index 000000000..f2e0b24a5 --- /dev/null +++ b/g/string/gstr/gstr_parse.go @@ -0,0 +1,157 @@ +package gstr + +import ( + "fmt" + "net/url" + "strings" +) + +// Parses the string into variables. +// f1=m&f2=n -> map[f1:m f2:n] +// f[a]=m&f[b]=n -> map[f:map[a:m b:n]] +// f[a][a]=m&f[a][b]=n -> map[f:map[a:map[a:m b:n]]] +// f[]=m&f[]=n -> map[f:[m n]] +// f[a][]=m&f[a][]=n -> map[f:map[a:[m n]]] +// f[][]=m&f[][]=n -> map[f:[map[]]] // Currently does not support nested slice. +// f=m&f[a]=n -> error // This is not the same as PHP. +// a .[[b=c -> map[a___[b:c] +func Parse(encodedString string, result map[string]interface{}) error { + // build nested map. + var build func(map[string]interface{}, []string, interface{}) error + build = func(result map[string]interface{}, keys []string, value interface{}) error { + length := len(keys) + // trim '," + key := strings.Trim(keys[0], "'\"") + if length == 1 { + result[key] = value + return nil + } + + // The end is slice. like f[], f[a][] + if keys[1] == "" && length == 2 { + // todo nested slice + if key == "" { + return nil + } + val, ok := result[key] + if !ok { + result[key] = []interface{}{value} + return nil + } + children, ok := val.([]interface{}) + if !ok { + return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val) + } + result[key] = append(children, value) + return nil + } + + // The end is slice + map. like f[][a] + if keys[1] == "" && length > 2 && keys[2] != "" { + val, ok := result[key] + if !ok { + result[key] = []interface{}{} + val = result[key] + } + children, ok := val.([]interface{}) + if !ok { + return fmt.Errorf("expected type '[]interface{}' for key '%s', but got '%T'", key, val) + } + if l := len(children); l > 0 { + if child, ok := children[l-1].(map[string]interface{}); ok { + if _, ok := child[keys[2]]; !ok { + build(child, keys[2:], value) + return nil + } + } + } + child := map[string]interface{}{} + build(child, keys[2:], value) + result[key] = append(children, child) + + return nil + } + + // map. like f[a], f[a][b] + val, ok := result[key] + if !ok { + result[key] = map[string]interface{}{} + val = result[key] + } + children, ok := val.(map[string]interface{}) + if !ok { + return fmt.Errorf("expected type 'map[string]interface{}' for key '%s', but got '%T'", key, val) + } + if err := build(children, keys[1:], value); err != nil { + return err + } + return nil + } + + // split encodedString. + parts := strings.Split(encodedString, "&") + for _, part := range parts { + pos := strings.Index(part, "=") + if pos <= 0 { + continue + } + key, err := url.QueryUnescape(part[:pos]) + if err != nil { + return err + } + for key[0] == ' ' { + key = key[1:] + } + if key == "" || key[0] == '[' { + continue + } + value, err := url.QueryUnescape(part[pos+1:]) + if err != nil { + return err + } + + // split into multiple keys + var keys []string + left := 0 + for i, k := range key { + if k == '[' && left == 0 { + left = i + } else if k == ']' { + if left > 0 { + if len(keys) == 0 { + keys = append(keys, key[:left]) + } + keys = append(keys, key[left+1:i]) + left = 0 + if i+1 < len(key) && key[i+1] != '[' { + break + } + } + } + } + if len(keys) == 0 { + keys = append(keys, key) + } + // first key + first := "" + for i, chr := range keys[0] { + if chr == ' ' || chr == '.' || chr == '[' { + first += "_" + } else { + first += string(chr) + } + if chr == '[' { + first += keys[0][i+1:] + break + } + } + keys[0] = first + + // build nested map + if err := build(result, keys, value); err != nil { + return err + } + } + + return nil +} \ No newline at end of file diff --git a/g/string/gstr/gstr_pos.go b/g/string/gstr/gstr_pos.go new file mode 100644 index 000000000..237535c8c --- /dev/null +++ b/g/string/gstr/gstr_pos.go @@ -0,0 +1,76 @@ +package gstr + +import "strings" + +// Find the position of the first occurrence of a substring in a string. +func Pos(haystack, needle string, offset int) int { + length := len(haystack) + if length == 0 || offset > length || -offset > length { + return -1 + } + + if offset < 0 { + offset += length + } + pos := strings.Index(haystack[offset:], needle) + if pos == -1 { + return -1 + } + return pos + offset +} + +// Find the position of the first occurrence of a case-insensitive substring in a string. +func PosI(haystack, needle string, offset int) int { + length := len(haystack) + if length == 0 || offset > length || -offset > length { + return -1 + } + + haystack = haystack[offset:] + if offset < 0 { + offset += length + } + pos := strings.Index(strings.ToLower(haystack), strings.ToLower(needle)) + if pos == -1 { + return -1 + } + return pos + offset +} + +// Find the position of the last occurrence of a substring in a string. +func PosR(haystack, needle string, offset int) int { + pos, length := 0, len(haystack) + if length == 0 || offset > length || -offset > length { + return -1 + } + + if offset < 0 { + haystack = haystack[:offset+length+1] + } else { + haystack = haystack[offset:] + } + pos = strings.LastIndex(haystack, needle) + if offset > 0 && pos != -1 { + pos += offset + } + return pos +} + +// Find the position of the last occurrence of a case-insensitive substring in a string. +func PosRI(haystack, needle string, offset int) int { + pos, length := 0, len(haystack) + if length == 0 || offset > length || -offset > length { + return -1 + } + + if offset < 0 { + haystack = haystack[:offset+length+1] + } else { + haystack = haystack[offset:] + } + pos = strings.LastIndex(strings.ToLower(haystack), strings.ToLower(needle)) + if offset > 0 && pos != -1 { + pos += offset + } + return pos +} \ No newline at end of file diff --git a/g/string/gstr/gstr_similartext.go b/g/string/gstr/gstr_similartext.go new file mode 100644 index 000000000..108a2a32a --- /dev/null +++ b/g/string/gstr/gstr_similartext.go @@ -0,0 +1,47 @@ +package gstr + +// Calculate the similarity between two strings. +// See http://php.net/manual/en/function.similar-text.php. +func SimilarText(first, second string, percent *float64) int { + var similarText func(string, string, int, int) int + similarText = func(str1, str2 string, len1, len2 int) int { + var sum, max int + pos1, pos2 := 0, 0 + + // Find the longest segment of the same section in two strings + for i := 0; i < len1; i++ { + for j := 0; j < len2; j++ { + for l := 0; (i+l < len1) && (j+l < len2) && (str1[i+l] == str2[j+l]); l++ { + if l+1 > max { + max = l + 1 + pos1 = i + pos2 = j + } + } + } + } + + if sum = max; sum > 0 { + if pos1 > 0 && pos2 > 0 { + sum += similarText(str1, str2, pos1, pos2) + } + if (pos1+max < len1) && (pos2+max < len2) { + s1 := []byte(str1) + s2 := []byte(str2) + sum += similarText(string(s1[pos1+max:]), string(s2[pos2+max:]), len1-pos1-max, len2-pos2-max) + } + } + + return sum + } + + l1, l2 := len(first), len(second) + if l1+l2 == 0 { + return 0 + } + sim := similarText(first, second, l1, l2) + if percent != nil { + *percent = float64(sim*200) / float64(l1+l2) + } + return sim +} diff --git a/g/string/gstr/gstr_soundex.go b/g/string/gstr/gstr_soundex.go new file mode 100644 index 000000000..cd7ec1ca0 --- /dev/null +++ b/g/string/gstr/gstr_soundex.go @@ -0,0 +1,52 @@ +package gstr + +// Calculate the soundex key of a string. +// See http://php.net/manual/en/function.soundex.php. +func Soundex(str string) string { + if str == "" { + panic("str: cannot be an empty string") + } + table := [26]rune{ + '0', '1', '2', '3', // A, B, C, D + '0', '1', '2', // E, F, G + '0', // H + '0', '2', '2', '4', '5', '5', // I, J, K, L, M, N + '0', '1', '2', '6', '2', '3', // O, P, Q, R, S, T + '0', '1', // U, V + '0', '2', // W, X + '0', '2', // Y, Z + } + last, code, small := -1, 0, 0 + sd := make([]rune, 4) + // build soundex string + for i := 0; i < len(str) && small < 4; i++ { + // ToUpper + char := str[i] + if char < '\u007F' && 'a' <= char && char <= 'z' { + code = int(char - 'a' + 'A') + } else { + code = int(char) + } + if code >= 'A' && code <= 'Z' { + if small == 0 { + sd[small] = rune(code) + small++ + last = int(table[code-'A']) + } else { + code = int(table[code-'A']) + if code != last { + if code != 0 { + sd[small] = rune(code) + small++ + } + last = code + } + } + } + } + // pad with "0" + for ; small < 4; small++ { + sd[small] = '0' + } + return string(sd) +} diff --git a/g/util/gstr/gstr_test.go b/g/string/gstr/gstr_z_bench_test.go similarity index 100% rename from g/util/gstr/gstr_test.go rename to g/string/gstr/gstr_z_bench_test.go diff --git a/g/util/gtest/gtest.go b/g/test/gtest/gtest.go similarity index 92% rename from g/util/gtest/gtest.go rename to g/test/gtest/gtest.go index ae4b5f674..1ee4667e2 100644 --- a/g/util/gtest/gtest.go +++ b/g/test/gtest/gtest.go @@ -33,12 +33,24 @@ func Case(t *testing.T, f func()) { // 断言判断, 相等 func Assert(value, expect interface{}) { - rv := reflect.ValueOf(value) - if rv.Kind() == reflect.Ptr { - if rv.IsNil() { + rvValue := reflect.ValueOf(value) + rvExpect := reflect.ValueOf(expect) + if rvValue.Kind() == reflect.Ptr { + if rvValue.IsNil() { value = nil } } + //if rvExpect.Kind() == reflect.Map { + // if rvValue.Kind() == reflect.Map { + // ksExpect := rvExpect.MapKeys() + // for _, k := range ksExpect { + // rvValue.M.MapIndex() + // m[String(k.Interface())] = rv.MapIndex(k).Interface() + // } + // } else { + // panic(fmt.Sprint(`[ASSERT] EXPECT VALUE TO BE A MAP`)) + // } + //} if fmt.Sprintf("%v", value) != fmt.Sprintf("%v", expect) { panic(fmt.Sprintf(`[ASSERT] EXPECT %v == %v`, value, expect)) } @@ -233,7 +245,7 @@ func getBacktrace(skip...int) string { if reg, _ := regexp.Compile(``); reg.MatchString(file) { continue } - if reg, _ := regexp.Compile("/g/util/gtest/.+$"); reg.MatchString(file) { + if reg, _ := regexp.Compile("/g/test/gtest/.+$"); reg.MatchString(file) { continue } if goRoot != "" { diff --git a/g/util/gconv/gconv.go b/g/util/gconv/gconv.go index 140fc1364..9ea0a8cc5 100644 --- a/g/util/gconv/gconv.go +++ b/g/util/gconv/gconv.go @@ -11,9 +11,9 @@ package gconv import ( - "strconv" "encoding/json" "gitee.com/johng/gf/g/encoding/gbinary" + "strconv" "strings" ) @@ -116,27 +116,10 @@ func Int(i interface{}) int { if i == nil { return 0 } - switch value := i.(type) { - case int: return value - case int8: return int(value) - case int16: return int(value) - case int32: return int(value) - case int64: return int(value) - case uint: return int(value) - case uint8: return int(value) - case uint16: return int(value) - case uint32: return int(value) - case uint64: return int(value) - case float32: return int(value) - case float64: return int(value) - case bool: - if value { - return 1 - } - return 0 - default: - return int(Float64(value)) + if v, ok := i.(int); ok { + return v } + return int(Int64(i)) } func Int8(i interface{}) int8 { @@ -146,7 +129,7 @@ func Int8(i interface{}) int8 { if v, ok := i.(int8); ok { return v } - return int8(Int(i)) + return int8(Int64(i)) } func Int16(i interface{}) int16 { @@ -156,7 +139,7 @@ func Int16(i interface{}) int16 { if v, ok := i.(int16); ok { return v } - return int16(Int(i)) + return int16(Int64(i)) } func Int32(i interface{}) int32 { @@ -166,7 +149,7 @@ func Int32(i interface{}) int32 { if v, ok := i.(int32); ok { return v } - return int32(Int(i)) + return int32(Int64(i)) } func Int64(i interface{}) int64 { @@ -176,60 +159,57 @@ func Int64(i interface{}) int64 { if v, ok := i.(int64); ok { return v } - return int64(Int(i)) -} - -func Uint(i interface{}) uint { - if i == nil { - return 0 - } switch value := i.(type) { - case int: - if value < 0 { - value = -value - } - return uint(value) - case int8: - if value < 0 { - value = -value - } - return uint(value) - case int16: - if value < 0 { - value = -value - } - return uint(value) - case int32: - if value < 0 { - value = -value - } - return uint(value) - case int64: - if value < 0 { - value = -value - } - return uint(value) - case uint: return value - case uint8: return uint(value) - case uint16: return uint(value) - case uint32: return uint(value) - case uint64: return uint(value) - case float32: return uint(value) - case float64: return uint(value) + case int: return int64(value) + case int8: return int64(value) + case int16: return int64(value) + case int32: return int64(value) + case int64: return value + case uint: return int64(value) + case uint8: return int64(value) + case uint16: return int64(value) + case uint32: return int64(value) + case uint64: return int64(value) + case float32: return int64(value) + case float64: return int64(value) case bool: if value { return 1 } return 0 default: - v := Float64(value) - if v < 0 { - v = -v + s := String(value) + // 按照十六进制解析 + if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { + if v, e := strconv.ParseInt(s[2 : ], 16, 64); e == nil { + return v + } } - return uint(v) + // 按照八进制解析 + if len(s) > 1 && s[0] == '0' { + if v, e := strconv.ParseInt(s[1 : ], 8, 64); e == nil { + return v + } + } + // 按照十进制解析 + if v, e := strconv.ParseInt(s, 10, 64); e == nil { + return v + } + // 按照浮点数解析 + return int64(Float64(value)) } } +func Uint(i interface{}) uint { + if i == nil { + return 0 + } + if v, ok := i.(uint); ok { + return v + } + return uint(Uint64(i)) +} + func Uint8(i interface{}) uint8 { if i == nil { return 0 @@ -237,7 +217,7 @@ func Uint8(i interface{}) uint8 { if v, ok := i.(uint8); ok { return v } - return uint8(Uint(i)) + return uint8(Uint64(i)) } func Uint16(i interface{}) uint16 { @@ -247,7 +227,7 @@ func Uint16(i interface{}) uint16 { if v, ok := i.(uint16); ok { return v } - return uint16(Uint(i)) + return uint16(Uint64(i)) } func Uint32(i interface{}) uint32 { @@ -257,17 +237,52 @@ func Uint32(i interface{}) uint32 { if v, ok := i.(uint32); ok { return v } - return uint32(Uint(i)) + return uint32(Uint64(i)) } func Uint64(i interface{}) uint64 { if i == nil { return 0 } - if v, ok := i.(uint64); ok { - return v + switch value := i.(type) { + case int: return uint64(value) + case int8: return uint64(value) + case int16: return uint64(value) + case int32: return uint64(value) + case int64: return uint64(value) + case uint: return uint64(value) + case uint8: return uint64(value) + case uint16: return uint64(value) + case uint32: return uint64(value) + case uint64: return value + case float32: return uint64(value) + case float64: return uint64(value) + case bool: + if value { + return 1 + } + return 0 + default: + s := String(value) + // 按照十六进制解析 + if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { + if v, e := strconv.ParseUint(s[2 : ], 16, 64); e == nil { + return v + } + } + // 按照八进制解析 + if len(s) > 1 && s[0] == '0' { + if v, e := strconv.ParseUint(s[1 : ], 8, 64); e == nil { + return v + } + } + // 按照十进制解析 + if v, e := strconv.ParseUint(s, 10, 64); e == nil { + return v + } + // 按照浮点数解析 + return uint64(Float64(value)) } - return uint64(Uint(i)) } func Float32 (i interface{}) float32 { diff --git a/g/util/gconv/gconv_struct.go b/g/util/gconv/gconv_struct.go index f02389a93..8b1ff55e4 100644 --- a/g/util/gconv/gconv_struct.go +++ b/g/util/gconv/gconv_struct.go @@ -10,7 +10,7 @@ import ( "errors" "fmt" "gitee.com/johng/gf/g/container/gset" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" "gitee.com/johng/gf/third/github.com/fatih/structs" "reflect" "strings" diff --git a/g/util/gconv/gconv_time.go b/g/util/gconv/gconv_time.go index 7211c368b..76289c62a 100644 --- a/g/util/gconv/gconv_time.go +++ b/g/util/gconv/gconv_time.go @@ -9,7 +9,7 @@ package gconv import ( "time" "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" ) // 将变量i转换为time.Time类型 diff --git a/g/util/gconv/gconv_z_unit_basic_test.go b/g/util/gconv/gconv_z_unit_basic_test.go index 8c44f5fac..cca3b2c54 100644 --- a/g/util/gconv/gconv_z_unit_basic_test.go +++ b/g/util/gconv/gconv_z_unit_basic_test.go @@ -8,7 +8,7 @@ package gconv_test import ( "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" ) diff --git a/g/util/gconv/gconv_z_unit_map_test.go b/g/util/gconv/gconv_z_unit_map_test.go index 2aaf8712c..530a0294f 100644 --- a/g/util/gconv/gconv_z_unit_map_test.go +++ b/g/util/gconv/gconv_z_unit_map_test.go @@ -8,7 +8,7 @@ package gconv_test import ( "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" ) diff --git a/g/util/gconv/gconv_z_unit_slice_test.go b/g/util/gconv/gconv_z_unit_slice_test.go index e27062295..a334f8966 100644 --- a/g/util/gconv/gconv_z_unit_slice_test.go +++ b/g/util/gconv/gconv_z_unit_slice_test.go @@ -8,7 +8,7 @@ package gconv_test import ( "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" ) diff --git a/g/util/gconv/gconv_z_unit_struct_test.go b/g/util/gconv/gconv_z_unit_struct_test.go index 31b87597b..0659ef336 100644 --- a/g/util/gconv/gconv_z_unit_struct_test.go +++ b/g/util/gconv/gconv_z_unit_struct_test.go @@ -9,7 +9,7 @@ package gconv_test import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" ) @@ -30,7 +30,7 @@ func Test_Struct_Basic1(t *testing.T) { params1 := g.Map{ "uid" : 1, "Name" : "john", - "siteurl" : "https://gfer.me", + "siteurl" : "https://goframe.org", "nick_name" : "johng", "PASS1" : "123", "PASS2" : "456", @@ -41,7 +41,7 @@ func Test_Struct_Basic1(t *testing.T) { gtest.Assert(user, &User{ Uid : 1, Name : "john", - Site_Url : "https://gfer.me", + Site_Url : "https://goframe.org", NickName : "johng", Pass1 : "123", Pass2 : "456", @@ -52,7 +52,7 @@ func Test_Struct_Basic1(t *testing.T) { params2 := g.Map { "uid" : 2, "name" : "smith", - "site-url" : "https://gfer.me", + "site-url" : "https://goframe.org", "nick name" : "johng", "password1" : "111", "password2" : "222", @@ -63,7 +63,7 @@ func Test_Struct_Basic1(t *testing.T) { gtest.Assert(user, &User{ Uid : 2, Name : "smith", - Site_Url : "https://gfer.me", + Site_Url : "https://goframe.org", NickName : "johng", Pass1 : "111", Pass2 : "222", @@ -86,7 +86,7 @@ func Test_Struct_Basic2(t *testing.T) { params := g.Map { "uid" : 1, "Name" : "john", - "site_url" : "https://gfer.me", + "site_url" : "https://goframe.org", "PASS1" : "123", "PASS2" : "456", } @@ -96,7 +96,7 @@ func Test_Struct_Basic2(t *testing.T) { gtest.Assert(user, &User{ Uid : 1, Name : "john", - SiteUrl : "https://gfer.me", + SiteUrl : "https://goframe.org", Pass1 : "123", Pass2 : "456", }) diff --git a/g/util/gconv/gconv_z_unit_time_test.go b/g/util/gconv/gconv_z_unit_time_test.go index 7234a1990..86c9bfc37 100644 --- a/g/util/gconv/gconv_z_unit_time_test.go +++ b/g/util/gconv/gconv_z_unit_time_test.go @@ -9,7 +9,7 @@ package gconv_test import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "testing" "time" ) diff --git a/g/util/gpage/gpage.go b/g/util/gpage/gpage.go index 3e45cfaaf..6b316ee35 100644 --- a/g/util/gpage/gpage.go +++ b/g/util/gpage/gpage.go @@ -15,8 +15,8 @@ import ( url2 "net/url" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/net/ghttp" - "gitee.com/johng/gf/g/util/gregex" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gregex" + "gitee.com/johng/gf/g/string/gstr" "strings" ) diff --git a/g/util/grand/grand.go b/g/util/grand/grand.go index d74cbc653..c085b73f5 100644 --- a/g/util/grand/grand.go +++ b/g/util/grand/grand.go @@ -24,23 +24,23 @@ func MeetProb(prob float32) bool { return Rand(0, 1e7) <= int(prob*1e7) } -// Rand 别名 +// Rand 别名, 返回: [min, max] func N (min, max int) int { return Rand(min, max) } -// 获得一个 min, max 之间的随机数(min <= x <= max) +// 获得一个 min, max 之间的随机数: [min, max] func Rand (min, max int) int { if min >= max { return min } if min >= 0 { // 数值往左平移,再使用底层随机方法获得随机数,随后将结果数值往右平移 - return intn(max - (min - 0) + 1) + (min - 0) + return Intn(max - (min - 0) + 1) + (min - 0) } if min < 0 { // 数值往右平移,再使用底层随机方法获得随机数,随后将结果数值往左平移 - return intn(max + (0 - min) + 1) - (0 - min) + return Intn(max + (0 - min) + 1) - (0 - min) } return 0 } @@ -54,10 +54,10 @@ func Str(n int) string { func RandStr(n int) string { b := make([]rune, n) for i := range b { - if intn(2) == 1 { - b[i] = digits[intn(10)] + if Intn(2) == 1 { + b[i] = digits[Intn(10)] } else { - b[i] = letters[intn(52)] + b[i] = letters[Intn(52)] } } return string(b) @@ -72,7 +72,7 @@ func Digits(n int) string { func RandDigits(n int) string { b := make([]rune, n) for i := range b { - b[i] = digits[intn(10)] + b[i] = digits[Intn(10)] } return string(b) } @@ -86,7 +86,18 @@ func Letters(n int) string { func RandLetters(n int) string { b := make([]rune, n) for i := range b { - b[i] = letters[intn(52)] + b[i] = letters[Intn(52)] } return string(b) } + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n). +func Perm(n int) []int { + m := make([]int, n) + for i := 0; i < n; i++ { + j := Intn(i + 1) + m[i] = m[j] + m[j] = i + } + return m +} diff --git a/g/util/grand/grand_intn.go b/g/util/grand/grand_intn.go index 2309b7b4b..06aa19c21 100644 --- a/g/util/grand/grand_intn.go +++ b/g/util/grand/grand_intn.go @@ -44,8 +44,8 @@ func init() { }() } -// 自定义的 rand.Intn ,绝对随机 -func intn (max int) int { +// 自定义的 rand.Intn ,绝对随机, 返回: [0, max) +func Intn (max int) int { n := int(<- bufferChan)%max if n < 0 { return -n diff --git a/g/util/gstr/gstr.go b/g/util/gstr/gstr.go deleted file mode 100644 index aec98176d..000000000 --- a/g/util/gstr/gstr.go +++ /dev/null @@ -1,184 +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 gstr provides useful API for string handling. -// -// 字符串操作. -package gstr - -import ( - "bytes" - "math" - "strings" -) - -// 字符串替换(大小写敏感) -func Replace(origin, search, replace string, count...int) string { - n := -1 - if len(count) > 0 { - n = count[0] - } - return strings.Replace(origin, search, replace, n) -} - -// 使用map进行字符串替换(大小写敏感) -func ReplaceByMap(origin string, replaces map[string]string) string { - result := origin - for k, v := range replaces { - result = strings.Replace(result, k, v, -1) - } - return result -} - -// 字符串转换为小写 -func ToLower(s string) string { - return strings.ToLower(s) -} - -// 字符串转换为大写 -func ToUpper(s string) string { - return strings.ToUpper(s) -} - -// 字符串首字母转换为大写 -func UcFirst(s string) string { - if len(s) == 0 { - return s - } - if IsLetterLower(s[0]) { - return string(s[0] - 32) + s[1 :] - } - return s -} - -// 字符串首字母转换为小写 -func LcFirst(s string) string { - if len(s) == 0 { - return s - } - if IsLetterUpper(s[0]) { - return string(s[0] + 32) + s[1 :] - } - return s -} - -// 便利数组查找字符串索引位置,如果不存在则返回-1,使用完整遍历查找 -func SearchArray (a []string, s string) int { - for i, v := range a { - if s == v { - return i - } - } - return -1 -} - -// 判断字符串是否在数组中 -func InArray (a []string, s string) bool { - return SearchArray(a, s) != -1 -} - -// 判断给定字符是否小写 -func IsLetterLower(b byte) bool { - if b >= byte('a') && b <= byte('z') { - return true - } - return false -} - -// 判断给定字符是否大写 -func IsLetterUpper(b byte) bool { - if b >= byte('A') && b <= byte('Z') { - return true - } - return false -} - -// 判断锁给字符串是否为数字 -func IsNumeric(s string) bool { - length := len(s) - if length == 0 { - return false - } - for i := 0; i < len(s); i++ { - if s[i] < byte('0') || s[i] > byte('9') { - return false - } - } - return true -} - -// 字符串截取,支持中文 -func SubStr(str string, start int, length...int) (substr string) { - // 将字符串的转换成[]rune - rs := []rune(str) - lth := len(rs) - // 简单的越界判断 - if start < 0 { - start = 0 - } - if start >= lth { - start = lth - } - end := lth - if len(length) > 0 { - end = start + length[0] - if end < start { - end = lth - } - } - if end > lth { - end = lth - } - // 返回子串 - return string(rs[start : end]) -} - -// 字符串长度截取限制,超过长度限制被截取并在字符串末尾追加指定的内容,支持中文 -func StrLimit(str string, length int, suffix...string) (string) { - rs := []rune(str) - if len(str) < length { - return str - } - addstr := "..." - if len(suffix) > 0 { - addstr = suffix[0] - } - return string(rs[0 : length]) + addstr -} - -// 按照百分比从字符串中间向两边隐藏字符(主要用于姓名、手机号、邮箱地址、身份证号等的隐藏),支持utf-8中文,支持email格式。 -func HideStr(str string, percent int, hide string) string { - array := strings.Split(str, "@") - if len(array) > 1 { - str = array[0] - } - rs := []rune(str) - length := len(rs) - mid := math.Floor(float64(length/2)) - hideLen := int(math.Floor(float64(length) * (float64(percent)/100))) - start := int(mid - math.Floor(float64(hideLen) / 2)) - hideStr := []rune("") - hideRune := []rune(hide) - for i := 0; i < int(hideLen); i++ { - hideStr = append(hideStr, hideRune...) - } - buffer := bytes.NewBuffer(nil) - buffer.WriteString(string(rs[0 : start])) - buffer.WriteString(string(hideStr)) - buffer.WriteString(string(rs[start + hideLen : ])) - if len(array) > 1 { - buffer.WriteString(array[1]) - } - return buffer.String() -} - -// 将\n\r替换为html中的
标签。 -func Nl2Br(str string) string { - str = Replace(str, "\r\n", "\n") - str = Replace(str, "\n\r", "\n") - str = Replace(str, "\n", "
") - return str -} diff --git a/g/util/gutil/gutil.go b/g/util/gutil/gutil.go index f14ba645f..6dfb4c139 100644 --- a/g/util/gutil/gutil.go +++ b/g/util/gutil/gutil.go @@ -57,8 +57,8 @@ func PrintBacktrace() { index := 1 buffer := bytes.NewBuffer(nil) for i := 0; i < 10000; i++ { - if _, cfile, cline, ok := runtime.Caller(i); ok { - buffer.WriteString(fmt.Sprintf(`%d. %s:%d%s`, index, cfile, cline, "\n")) + if _, path, line, ok := runtime.Caller(i); ok { + buffer.WriteString(fmt.Sprintf(`%d. %s:%d%s`, index, path, line, "\n")) index++ } else { break @@ -84,3 +84,4 @@ func TryCatch(try func(), catch ... func(exception interface{})) { try() } + diff --git a/g/util/gvalid/gvalid.go b/g/util/gvalid/gvalid.go index d94eaf42a..a169d0fac 100644 --- a/g/util/gvalid/gvalid.go +++ b/g/util/gvalid/gvalid.go @@ -10,7 +10,7 @@ package gvalid import ( - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "strings" ) diff --git a/g/util/gvalid/gvalid_check.go b/g/util/gvalid/gvalid_check.go index 6e06c779b..ce23a6128 100644 --- a/g/util/gvalid/gvalid_check.go +++ b/g/util/gvalid/gvalid_check.go @@ -13,7 +13,7 @@ import ( "gitee.com/johng/gf/g/net/gipv6" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/string/gregex" "regexp" "strconv" "strings" diff --git a/g/util/gvalid/gvalid_unit_basic_all_test.go b/g/util/gvalid/gvalid_unit_basic_all_test.go index 3627d30a5..b6e49ec23 100644 --- a/g/util/gvalid/gvalid_unit_basic_all_test.go +++ b/g/util/gvalid/gvalid_unit_basic_all_test.go @@ -8,7 +8,7 @@ package gvalid_test import ( "gitee.com/johng/gf/g" - "gitee.com/johng/gf/g/util/gtest" + "gitee.com/johng/gf/g/test/gtest" "gitee.com/johng/gf/g/util/gvalid" "testing" ) diff --git a/geg/container/gpool/gpool.go b/geg/container/gpool/gpool.go index 94acd6cba..4b195e5b0 100644 --- a/geg/container/gpool/gpool.go +++ b/geg/container/gpool/gpool.go @@ -8,7 +8,7 @@ import ( func main () { // 创建一个对象池,过期时间为1000毫秒 - p := gpool.New(1000) + p := gpool.New(1000, nil) // 从池中取一个对象,返回nil及错误信息 fmt.Println(p.Get()) diff --git a/geg/net/ghttp/server/hooks/same_route_multi_hook.go b/geg/net/ghttp/server/hooks/same_route_multi_hook.go index 99a3dfc55..cb7a056ad 100644 --- a/geg/net/ghttp/server/hooks/same_route_multi_hook.go +++ b/geg/net/ghttp/server/hooks/same_route_multi_hook.go @@ -13,7 +13,7 @@ func beforeServeHook1(r *ghttp.Request) { // 随后调用的HOOK func beforeServeHook2(r *ghttp.Request) { - r.SetParam("site", "https://gfer.me") + r.SetParam("site", "https://goframe.org") r.Response.Writeln("set site") } diff --git a/geg/os/gview/build_in_funcs/build_in_funcs.go b/geg/os/gview/build_in_funcs/build_in_funcs.go index 07460aef0..1c8cc7ea6 100644 --- a/geg/os/gview/build_in_funcs/build_in_funcs.go +++ b/geg/os/gview/build_in_funcs/build_in_funcs.go @@ -10,8 +10,8 @@ func main() { {{"
测试
"|text}} {{"
测试
"|html}} {{"<div>测试</div>"|htmldecode}} -{{"https://gfer.me"|url}} -{{"https%3A%2F%2Fgfer.me"|urldecode}} +{{"https://goframe.org"|url}} +{{"https%3A%2F%2Fgoframe.org"|urldecode}} {{1540822968 | date "Y-m-d"}} {{"1540822968" | date "Y-m-d H:i:s"}} {{date "Y-m-d H:i:s"}} diff --git a/geg/other/test.go b/geg/other/test.go index 5457752ea..d5cfc1631 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,25 +1,10 @@ package main import ( - "gitee.com/johng/gf/g" - "gitee.com/johng/gf/g/net/ghttp" + "fmt" + "gitee.com/johng/gf/g/string/gstr" ) -func Handler(r *ghttp.Request) { - if r.Request.Method == "OPTIONS" { - return - } - r.Response.WriteJson(g.Map{"name" : "john"}) -} - func main() { - s := g.Server() - s.BindHookHandler("/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) { - r.Response.SetAllowCrossDomainRequest("*", "PUT,GET,POST,DELETE,OPTIONS") - r.Response.Header().Set("Access-Control-Allow-Credentials", "true") - r.Response.Header().Set("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, token") - }) - s.Group("/v1").ALL("*", Handler, ghttp.HOOK_BEFORE_SERVE) - s.SetPort(6789) - s.Run() + fmt.Println(gstr.PosI("abcdEfg", "eF", 0)) } \ No newline at end of file diff --git a/geg/util/gconv/gconv_struct1.go b/geg/util/gconv/gconv_struct1.go index cee49b7c1..b75161583 100644 --- a/geg/util/gconv/gconv_struct1.go +++ b/geg/util/gconv/gconv_struct1.go @@ -22,7 +22,7 @@ func main() { params1 := g.Map{ "uid" : 1, "Name" : "john", - "siteurl" : "https://gfer.me", + "siteurl" : "https://goframe.org", "nick_name" : "johng", "PASS1" : "123", "PASS2" : "456", @@ -36,7 +36,7 @@ func main() { params2 := g.Map { "uid" : 2, "name" : "smith", - "site-url" : "https://gfer.me", + "site-url" : "https://goframe.org", "nick name" : "johng", "password1" : "111", "password2" : "222", diff --git a/geg/util/gconv/gconv_struct2.go b/geg/util/gconv/gconv_struct2.go index 17e15fdcf..89d83819b 100644 --- a/geg/util/gconv/gconv_struct2.go +++ b/geg/util/gconv/gconv_struct2.go @@ -21,7 +21,7 @@ func main() { params := g.Map { "uid" : 1, "Name" : "john", - "site_url" : "https://gfer.me", + "site_url" : "https://goframe.org", "PASS1" : "123", "PASS2" : "456", } diff --git a/geg/util/gpage/gpage_custom1.go b/geg/util/gpage/gpage_custom1.go index 532a59758..34762e44d 100644 --- a/geg/util/gpage/gpage_custom1.go +++ b/geg/util/gpage/gpage_custom1.go @@ -3,7 +3,7 @@ package main import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/os/gview" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/util/gpage" ) diff --git a/geg/util/gstr/gstr_hidestr.go b/geg/util/gstr/gstr_hidestr.go index 3cc38d4f2..700b64be2 100644 --- a/geg/util/gstr/gstr_hidestr.go +++ b/geg/util/gstr/gstr_hidestr.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" ) func main() { diff --git a/geg/util/gstr/gstr_substr.go b/geg/util/gstr/gstr_substr.go index db59f4ad7..bed4c75c0 100644 --- a/geg/util/gstr/gstr_substr.go +++ b/geg/util/gstr/gstr_substr.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "gitee.com/johng/gf/g/util/gstr" + "gitee.com/johng/gf/g/string/gstr" ) func main() { diff --git a/third/README.MD b/third/README.MD index 7799e3372..bb95cfc4f 100644 --- a/third/README.MD +++ b/third/README.MD @@ -1 +1 @@ -`GF` self-maintains its thirdparty-packages, developers do not need worry about the dependences. \ No newline at end of file +`GF` self-maintains its thirdparty-packages, developers need no worry about the dependences. \ No newline at end of file