merge qiangg_php

This commit is contained in:
John
2019-02-01 00:04:34 +08:00
130 changed files with 4855 additions and 1282 deletions

View File

@ -1,10 +1,10 @@
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
<img align="right" height="150px" src="https://goframe.org/cover.png">
[![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
<div align=center>
<img src="https://gfer.me/images/arch.png"/>
<img src="https://goframe.org/images/arch.png"/>
</div>
# 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() {
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://gfer.me/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://goframe.org/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
@ -84,7 +84,7 @@ func main() {
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://gfer.me/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://goframe.org/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>

View File

@ -1,10 +1,10 @@
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
<img align="right" height="150px" src="https://goframe.org/cover.png">
[![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
# 架构
<div align=center>
<img src="https://gfer.me/images/arch.png"/>
<img src="https://goframe.org/images/arch.png"/>
</div>
@ -74,7 +72,7 @@ func main() {
}
```
[更多..](https://gfer.me/start/index)
[更多..](https://goframe.org/start/index)
# 协议
@ -86,14 +84,14 @@ func main() {
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png" width="60" align="left"></a>
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://gfer.me/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://goframe.org/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://gfer.me/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://goframe.org/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
<br /><br /><br />

View File

@ -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`下搜索失效问题;

View File

@ -50,6 +50,8 @@
1. 增加SO_REUSEPORT的支持
1. 权限管理模块;
1. 从ghttp中剥离SESSION功能构成单独的模块gsession
1. 改进gproc进程间通信处理逻辑提高稳定性以应对进程间大批量的数据发送/接收;

View File

@ -6,7 +6,7 @@
// Package garray provides kinds of concurrent-safe(alternative) arrays.
//
// 并发安全数组.
// 并发安全数组.
package garray
func New(size int, cap int, unsafe...bool) *Array {

View File

@ -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

View File

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

View File

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

View File

@ -0,0 +1,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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

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

View File

@ -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)

View File

@ -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")
})
}

View File

@ -7,7 +7,7 @@
// Package glist provides a concurrent-safe(alternative) doubly linked list.
//
// 并发安全双向链表.
// 并发安全双向链表.
package glist
import (

View File

@ -6,7 +6,7 @@
// Package gmap provides kinds of concurrent-safe(alternative) maps.
//
// 并发安全的哈希MAP.
// 并发安全MAP.
package gmap
// 默认的Map对象其实就是InterfaceInterfaceMap的别名。

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -6,7 +6,7 @@
// Package gqueue provides a dynamic/static concurrent-safe(alternative) queue.
//
// 并发安全动态队列.
// 并发安全动态队列.
//
// 特点:
// 1. 动态队列初始化速度快;

View File

@ -9,9 +9,226 @@
// 并发安全集合.
package gset
type Set = InterfaceSet
import (
"fmt"
"gitee.com/johng/gf/g/internal/rwmutex"
)
type Set struct {
mu *rwmutex.RWMutex
m map[interface{}]struct{}
}
// 默认Set类型
func New(unsafe...bool) *Set {
return NewInterfaceSet(unsafe...)
return NewSet(unsafe...)
}
func NewSet(unsafe...bool) *Set {
return &Set{
m : make(map[interface{}]struct{}),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (set *Set) Iterator(f func (v interface{}) bool) *Set {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
return set
}
// 添加
func (set *Set) Add(item interface{}) *Set {
set.mu.Lock()
set.m[item] = struct{}{}
set.mu.Unlock()
return set
}
// 批量添加
func (set *Set) BatchAdd(items []interface{}) *Set {
set.mu.Lock()
for _, item := range items {
set.m[item] = struct{}{}
}
set.mu.Unlock()
return set
}
// 键是否存在
func (set *Set) Contains(item interface{}) bool {
set.mu.RLock()
_, exists := set.m[item]
set.mu.RUnlock()
return exists
}
// 删除键值对
func (set *Set) Remove(key interface{}) *Set {
set.mu.Lock()
delete(set.m, key)
set.mu.Unlock()
return set
}
// 大小
func (set *Set) Size() int {
set.mu.RLock()
l := len(set.m)
set.mu.RUnlock()
return l
}
// 清空set
func (set *Set) Clear() *Set {
set.mu.Lock()
set.m = make(map[interface{}]struct{})
set.mu.Unlock()
return set
}
// 转换为数组
func (set *Set) Slice() []interface{} {
set.mu.RLock()
i := 0
ret := make([]interface{}, len(set.m))
for item := range set.m {
ret[i] = item
i++
}
set.mu.RUnlock()
return ret
}
// 转换为字符串
func (set *Set) String() string {
return fmt.Sprint(set.Slice())
}
func (set *Set) LockFunc(f func(m map[interface{}]struct{})) *Set {
set.mu.Lock(true)
defer set.mu.Unlock(true)
f(set.m)
return set
}
func (set *Set) RLockFunc(f func(m map[interface{}]struct{})) *Set {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
f(set.m)
return set
}
// 判断两个集合是否相等.
func (set *Set) Equal(other *Set) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
if len(set.m) != len(other.m) {
return false
}
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// 判断当前集合是否为other集合的子集.
func (set *Set) IsSubsetOf(other *Set) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// 并集, 返回新的集合属于set或属于other的元素为元素的集合.
func (set *Set) Union(other *Set) (newSet *Set) {
newSet = NewSet(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 *Set) Diff(other *Set) (newSet *Set) {
newSet = NewSet(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 *Set) Inter(other *Set) (newSet *Set) {
newSet = NewSet(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的元素组成的集合.
// 如果给定的full集合不是set的全集时返回full与set的差集.
func (set *Set) Complement(full *Set) (newSet *Set) {
newSet = NewSet(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
}

View File

@ -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
}
// 转换为数组
@ -89,11 +92,10 @@ func (set *IntSet) Slice() []int {
set.mu.RLock()
ret := make([]int, len(set.m))
i := 0
for item := range set.m {
ret[i] = item
for k, _ := range set.m {
ret[i] = k
i++
}
set.mu.RUnlock()
return ret
}
@ -103,14 +105,125 @@ 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) IsSubsetOf(other *IntSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// 并集, 返回新的集合属于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的元素组成的集合.
// 如果给定的full集合不是set的全集时返回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
}

View File

@ -1,114 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gset
import (
"fmt"
"gitee.com/johng/gf/g/internal/rwmutex"
)
type InterfaceSet struct {
mu *rwmutex.RWMutex
m map[interface{}]struct{}
}
func NewInterfaceSet(unsafe...bool) *InterfaceSet {
return &InterfaceSet{
m : make(map[interface{}]struct{}),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (set *InterfaceSet) Iterator(f func (v interface{}) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
}
// 添加
func (set *InterfaceSet) Add(item interface{}) *InterfaceSet {
set.mu.Lock()
set.m[item] = struct{}{}
set.mu.Unlock()
return set
}
// 批量添加
func (set *InterfaceSet) BatchAdd(items []interface{}) *InterfaceSet {
set.mu.Lock()
for _, item := range items {
set.m[item] = struct{}{}
}
set.mu.Unlock()
return set
}
// 键是否存在
func (set *InterfaceSet) Contains(item interface{}) bool {
set.mu.RLock()
_, exists := set.m[item]
set.mu.RUnlock()
return exists
}
// 删除键值对
func (set *InterfaceSet) Remove(key interface{}) {
set.mu.Lock()
delete(set.m, key)
set.mu.Unlock()
}
// 大小
func (set *InterfaceSet) Size() int {
set.mu.RLock()
l := len(set.m)
set.mu.RUnlock()
return l
}
// 清空set
func (set *InterfaceSet) Clear() {
set.mu.Lock()
set.m = make(map[interface{}]struct{})
set.mu.Unlock()
}
// 转换为数组
func (set *InterfaceSet) Slice() []interface{} {
set.mu.RLock()
i := 0
ret := make([]interface{}, len(set.m))
for item := range set.m {
ret[i] = item
i++
}
set.mu.RUnlock()
return ret
}
// 转换为字符串
func (set *InterfaceSet) String() string {
return fmt.Sprint(set.Slice())
}
func (set *InterfaceSet) LockFunc(f func(m map[interface{}]struct{})) {
set.mu.Lock(true)
defer set.mu.Unlock(true)
f(set.m)
}
func (set *InterfaceSet) RLockFunc(f func(m map[interface{}]struct{})) {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
f(set.m)
}

View File

@ -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,125 @@ 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) IsSubsetOf(other *StringSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// 并集, 返回新的集合属于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的元素组成的集合.
// 如果给定的full集合不是set的全集时返回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
}

View File

@ -15,7 +15,7 @@ import (
)
var ints = gset.NewIntSet()
var itfs = gset.NewInterfaceSet()
var itfs = gset.NewSet()
var strs = gset.NewStringSet()
func Benchmark_IntSet_Add(b *testing.B) {
@ -36,19 +36,19 @@ func Benchmark_IntSet_Remove(b *testing.B) {
}
}
func Benchmark_InterfaceSet_Add(b *testing.B) {
func Benchmark_Set_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Add(i)
}
}
func Benchmark_InterfaceSet_Contains(b *testing.B) {
func Benchmark_Set_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Contains(i)
}
}
func Benchmark_InterfaceSet_Remove(b *testing.B) {
func Benchmark_Set_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Remove(i)
}

View File

@ -15,7 +15,7 @@ import (
)
var intsUnsafe = gset.NewIntSet(true)
var itfsUnsafe = gset.NewInterfaceSet(true)
var itfsUnsafe = gset.NewSet(true)
var strsUnsafe = gset.NewStringSet(true)
func Benchmark_Unsafe_IntSet_Add(b *testing.B) {
@ -36,19 +36,19 @@ func Benchmark_Unsafe_IntSet_Remove(b *testing.B) {
}
}
func Benchmark_Unsafe_InterfaceSet_Add(b *testing.B) {
func Benchmark_Unsafe_Set_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_InterfaceSet_Contains(b *testing.B) {
func Benchmark_Unsafe_Set_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_InterfaceSet_Remove(b *testing.B) {
func Benchmark_Unsafe_Set_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Remove(i)
}

View File

@ -0,0 +1,160 @@
// 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/garray"
"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(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestIntSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
a1 := garray.New(0, 0)
a2 := garray.New(0, 0)
s.Iterator(func(v int) bool {
a1.Append(1)
return false
})
s.Iterator(func(v int) bool {
a2.Append(1)
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestIntSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[int]struct{}) {
delete(m, 1)
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[int]struct{}) {
gtest.Assert(m, map[int]struct{}{
3 : struct{}{},
2 : struct{}{},
})
})
})
}
func TestIntSet_Equal(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s3 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestIntSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s3 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestIntSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s3 := s1.Union(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), true)
})
}
func TestIntSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), false)
gtest.Assert(s3.Contains(4), false)
})
}
func TestIntSet_Inter(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Inter(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), false)
})
}
func TestIntSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(4), true)
gtest.Assert(s3.Contains(5), true)
})
}

View File

@ -0,0 +1,160 @@
// 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/garray"
"gitee.com/johng/gf/g/container/gset"
"gitee.com/johng/gf/g/test/gtest"
"testing"
)
func TestStringSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("1").Add("2")
s.BatchAdd([]string{"3","4"})
gtest.Assert(s.Size(), 4)
gtest.AssertIN("1", s.Slice())
gtest.AssertIN("2", s.Slice())
gtest.AssertIN("3", s.Slice())
gtest.AssertIN("4", s.Slice())
gtest.AssertNI("0", s.Slice())
gtest.Assert(s.Contains("4"), true)
gtest.Assert(s.Contains("5"), false)
s.Remove("1")
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestStringSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("2").Add("3")
gtest.Assert(s.Size(), 3)
a1 := garray.New(0, 0)
a2 := garray.New(0, 0)
s.Iterator(func(v string) bool {
a1.Append("1")
return false
})
s.Iterator(func(v string) bool {
a2.Append("1")
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestStringSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("2").Add("3")
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[string]struct{}) {
delete(m, "1")
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[string]struct{}) {
gtest.Assert(m, map[string]struct{}{
"3" : struct{}{},
"2" : struct{}{},
})
})
})
}
func TestStringSet_Equal(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s3 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestStringSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s3 := gset.NewStringSet()
s1.Add("1").Add("2")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestStringSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2")
s2.Add("3").Add("4")
s3 := s1.Union(s2)
gtest.Assert(s3.Contains("1"), true)
gtest.Assert(s3.Contains("2"), true)
gtest.Assert(s3.Contains("3"), true)
gtest.Assert(s3.Contains("4"), true)
})
}
func TestStringSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains("1"), true)
gtest.Assert(s3.Contains("2"), true)
gtest.Assert(s3.Contains("3"), false)
gtest.Assert(s3.Contains("4"), false)
})
}
func TestStringSet_Inter(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Inter(s2)
gtest.Assert(s3.Contains("1"), false)
gtest.Assert(s3.Contains("2"), false)
gtest.Assert(s3.Contains("3"), true)
gtest.Assert(s3.Contains("4"), false)
})
}
func TestStringSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains("1"), false)
gtest.Assert(s3.Contains("2"), false)
gtest.Assert(s3.Contains("4"), true)
gtest.Assert(s3.Contains("5"), true)
})
}

View File

@ -0,0 +1,160 @@
// 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/garray"
"gitee.com/johng/gf/g/container/gset"
"gitee.com/johng/gf/g/test/gtest"
"testing"
)
func TestSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(1).Add(2)
s.BatchAdd([]interface{}{3,4})
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
a1 := garray.New(0, 0)
a2 := garray.New(0, 0)
s.Iterator(func(v interface{}) bool {
a1.Append(1)
return false
})
s.Iterator(func(v interface{}) bool {
a2.Append(1)
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[interface{}]struct{}) {
delete(m, 1)
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[interface{}]struct{}) {
gtest.Assert(m, map[interface{}]struct{}{
3 : struct{}{},
2 : struct{}{},
})
})
})
}
func TestSet_Equal(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s3 := s1.Union(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), true)
})
}
func TestSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), false)
gtest.Assert(s3.Contains(4), false)
})
}
func TestSet_Inter(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Inter(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), false)
})
}
func TestSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(4), true)
gtest.Assert(s3.Contains(5), true)
})
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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"
)

View File

@ -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"

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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 (

View File

@ -5,7 +5,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"
)

View File

@ -5,7 +5,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"
)

View File

@ -5,7 +5,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"
)

View File

@ -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"

View File

@ -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
}

View File

@ -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"

View File

@ -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 (

View File

@ -9,7 +9,7 @@
package ghttp
import (
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/string/gregex"
"time"
"bytes"
"strings"

View File

@ -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"

View File

@ -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"

View File

@ -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"
)

View File

@ -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"

View File

@ -10,7 +10,7 @@ package ghttp
import (
"strings"
"container/list"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/string/gregex"
)
// 查询请求处理方法.

View File

@ -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接口

View File

@ -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"

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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

View File

@ -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 {

View File

@ -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"
)

View File

@ -63,8 +63,13 @@ func (c *Config) filePath(file...string) (path string) {
}
c.paths.RLockFunc(func(array []string) {
for _, v := range array {
//fmt.Println("search:", v, name)
if path, _ = gspath.Search(v, name); path != "" {
break
} else {
//if strings.EqualFold(v, "/Users/john/Temp/config") {
// gutil.Dump(gspath.Get(v).AllPaths())
//}
}
}
})

View File

@ -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"

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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()
}
}

View File

@ -8,13 +8,14 @@
package glog
import (
"errors"
"fmt"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gfile"
"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"
@ -160,6 +161,10 @@ func (l *Logger) getFilePointer() *gfpool.File {
// 设置日志文件的存储目录路径
func (l *Logger) SetPath(path string) error {
// path必须有值
if path == "" {
return errors.New("path is empty")
}
// 如果目录不存在,则递归创建
if !gfile.Exists(path) {
if err := gfile.Mkdir(path); err != nil {

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
@ -47,7 +47,9 @@ func New(path...string) *SPath {
cache : gmap.NewStringStringMap(),
}
if len(path) > 0 {
sp.Add(path[0])
if _, err := sp.Add(path[0]); err != nil {
//fmt.Errorf(err.Error())
}
}
return sp
}
@ -111,6 +113,7 @@ func (sp *SPath) Add(path string) (realPath string, err error) {
}
// 添加的搜索路径必须为目录
if gfile.IsDir(realPath) {
//fmt.Println("gspath:", realPath, sp.paths.Search(realPath))
// 如果已经添加则不再添加
if sp.paths.Search(realPath) < 0 {
realPath = strings.TrimRight(realPath, gfile.Separator)
@ -223,9 +226,12 @@ func (sp *SPath) addToCache(filePath, rootPath string) {
// 如果添加的是目录,那么需要递归添加
if idDir {
if files, err := gfile.ScanDir(filePath, "*", true); err == nil {
//fmt.Println("gspath add to cache:", filePath, files)
for _, path := range files {
sp.cache.SetIfNotExist(sp.nameFromPath(path, rootPath), sp.makeCacheValue(path, gfile.IsDir(path)))
}
} else {
//fmt.Errorf(err.Error())
}
}
}

View File

@ -11,8 +11,7 @@ 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"
"regexp"
"strconv"
"strings"
@ -137,7 +136,7 @@ func parseDateStr(s string) (year, month, day int) {
return
}
// 判断年份在开头还是末尾
if gstr.IsNumeric(array[1]) {
if isNumeric(array[1]) {
year, _ = strconv.Atoi(array[0])
month, _ = strconv.Atoi(array[1])
day, _ = strconv.Atoi(array[2])
@ -307,4 +306,18 @@ func FuncCost(f func()) int64 {
t := Nanosecond()
f()
return Nanosecond() - t
}
// 判断锁给字符串是否为数字
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
}

View File

@ -8,7 +8,7 @@ package gtime
import (
"bytes"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/string/gregex"
"strings"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -10,26 +10,9 @@
package gregex
import (
"gitee.com/johng/gf/g/container/gmap"
"regexp"
)
// 缓存对象主要用于缓存底层regx对象
var regxCache = gmap.NewStringInterfaceMap()
// 根据pattern生成对应的regexp正则对象
func getRegexp(pattern string) (*regexp.Regexp, error) {
if v := regxCache.Get(pattern); v != nil {
return v.(*regexp.Regexp), nil
}
if r, err := regexp.Compile(pattern); err == nil {
regxCache.Set(pattern, r)
return r, nil
} else {
return nil, err
}
}
// 转移正则规则字符串例如Quote(`[foo]`) 返回 `\[foo\]`
func Quote(s string) string {
return regexp.QuoteMeta(s)

View File

@ -0,0 +1,46 @@
// 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 gregex
import (
"regexp"
"sync"
)
// 缓存对象主要用于缓存底层regx对象
var (
regexMu = sync.RWMutex{}
regexMap = make(map[string]*regexp.Regexp)
)
// 根据pattern生成对应的regexp正则对象
func getRegexp(pattern string) (*regexp.Regexp, error) {
if r := getCache(pattern); r != nil {
return r, nil
}
if r, err := regexp.Compile(pattern); err == nil {
setCache(pattern, r)
return r, nil
} else {
return nil, err
}
}
// 获得正则缓存对象
func getCache(pattern string) (regex *regexp.Regexp) {
regexMu.RLock()
regex = regexMap[pattern]
regexMu.RUnlock()
return
}
// 设置正则缓存对象
func setCache(pattern string, regex *regexp.Regexp) {
regexMu.Lock()
regexMap[pattern] = regex
regexMu.Unlock()
}

View File

@ -0,0 +1,59 @@
// Copyright 2017-2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// go test *.go -bench=".*"
package gregex
import (
"testing"
)
var pattern = `(.+):(\d+)`
var src = "johng.cn:80"
var replace = "johng.cn"
func BenchmarkValidate(b *testing.B) {
for i := 0; i < b.N; i++ {
Validate(pattern)
}
}
func BenchmarkIsMatch(b *testing.B) {
for i := 0; i < b.N; i++ {
IsMatch(pattern, []byte(src))
}
}
func BenchmarkIsMatchString(b *testing.B) {
for i := 0; i < b.N; i++ {
IsMatchString(pattern, src)
}
}
func BenchmarkMatchString(b *testing.B) {
for i := 0; i < b.N; i++ {
MatchString(pattern, src)
}
}
func BenchmarkMatchAllString(b *testing.B) {
for i := 0; i < b.N; i++ {
MatchAllString(pattern, src)
}
}
func BenchmarkReplace(b *testing.B) {
for i := 0; i < b.N; i++ {
Replace(pattern, []byte(replace), []byte(src))
}
}
func BenchmarkReplaceString(b *testing.B) {
for i := 0; i < b.N; i++ {
ReplaceString(pattern, replace, src)
}
}

622
g/string/gstr/gstr.go Normal file
View File

@ -0,0 +1,622 @@
// 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)
}
// Traverse the array to find the string index position, if not exist, return-1.
//
// 遍历数组查找字符串索引位置,如果不存在则返回-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.
// See http://php.net/manual/en/function.number-format.php.
//
// 以千位分隔符方式格式化一个数字.
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.
// Can be used to split a string into smaller chunks which is useful for
// e.g. converting BASE64 string output to match RFC 2045 semantics.
// It inserts end every chunkLen characters.
//
// 将字符串分割成小块。使用此函数将字符串分割成小块非常有用。
// 例如将BASE64的输出转换成符合RFC2045语义的字符串。
// 它会在每 chunkLen 个字符后边插入 end。
func ChunkSplit(body string, chunkLen int, end string) string {
if end == "" {
end = "\r\n"
}
runes, endRunes := []rune(body), []rune(end)
l := len(runes)
if l <= 1 || l < chunkLen {
return body + end
}
ns := make([]rune, 0, len(runes) + len(endRunes))
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.
// TODO: Enable cut param, see http://php.net/manual/en/function.wordwrap.php.
func WordWrap(str string, width int, br string) string {
if br == "" {
br = "\n"
}
init := make([]byte, 0, len(str))
buf := bytes.NewBuffer(init)
var current int
var wordBuf, spaceBuf bytes.Buffer
for _, char := range str {
if char == '\n' {
if wordBuf.Len() == 0 {
if current + spaceBuf.Len() > width {
current = 0
} else {
current += spaceBuf.Len()
spaceBuf.WriteTo(buf)
}
spaceBuf.Reset()
} else {
current += 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 += spaceBuf.Len() + wordBuf.Len()
spaceBuf.WriteTo(buf)
spaceBuf.Reset()
wordBuf.WriteTo(buf)
wordBuf.Reset()
}
spaceBuf.WriteRune(char)
} else {
wordBuf.WriteRune(char)
if current + spaceBuf.Len()+wordBuf.Len() > width && wordBuf.Len() < width {
buf.WriteString(br)
current = 0
spaceBuf.Reset()
}
}
}
if wordBuf.Len() == 0 {
if current + 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.
//
// 查找字符串的首次出现。返回 haystack 字符串从 needle 第一次出现的位置开始到 haystack 结尾的字符串。
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.
// See http://php.net/manual/en/function.strtr.php.
//
// 转换或者替换指定字符。
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 {
if len(characterMask) > 0 {
return strings.Trim(str, characterMask[0])
} else {
return strings.TrimSpace(str)
}
}
// Strip whitespace (or other characters) from the beginning of a string.
//
// 去除字符串首的空白字符(或者其他字符)。
func TrimLeft(str string, characterMask ...string) string {
mask := ""
if len(characterMask) == 0 {
mask = string([]byte{'\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0})
} 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 = string([]byte{'\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0})
} else {
mask = characterMask[0]
}
return strings.TrimRight(str, mask)
}
// Split a string by a string, to an array.
//
// 此函数返回由字符串组成的数组,每个元素都是 str 的一个子串,它们被字符串 delimiter 作为边界点分割出来。
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.
//
// 用sep将字符串数组array连接为一个字符串。
func Join(array []string, sep string) string {
return strings.Join(array, sep)
}
// Split a string by a string, to an array.
// See http://php.net/manual/en/function.explode.php.
//
// 此函数返回由字符串组成的数组,每个元素都是 str 的一个子串,它们被字符串 delimiter 作为边界点分割出来。
func Explode(delimiter, str string) []string {
return Split(str, delimiter)
}
// Join array elements with a string.
// http://php.net/manual/en/function.implode.php
//
// 用glue将字符串数组pieces连接为一个字符串。
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.
//
// 返回相对应于 ascii 所指定的单个字符。
func Chr(ascii int) string {
return string(ascii)
}
// Convert the first byte of a string to a value between 0 and 255.
//
// 解析 char 二进制值第一个字节为 0 到 255 范围的无符号整型类型。
func Ord(char string) int {
return int(char[0])
}
// 按照百分比从字符串中间向两边隐藏字符(主要用于姓名、手机号、邮箱地址、身份证号等的隐藏)支持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
//
// 在字符串 string 所有新行之前插入 '<br />' 或 '<br>',并返回。
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("<br />")
} else {
br = []byte("<br>")
}
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.
//
// 转义字符串中的单引号(')、双引号(")、反斜线(\)与 NULNULL 字符)。
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()
}

View File

@ -0,0 +1,57 @@
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.
//
// 计算两个字符串之间的编辑距离(Levenshtein distance)。
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
}

159
g/string/gstr/gstr_parse.go Normal file
View File

@ -0,0 +1,159 @@
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]
//
// 将字符串解析成Map。
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
}

100
g/string/gstr/gstr_pos.go Normal file
View File

@ -0,0 +1,100 @@
package gstr
import "strings"
// Find the position of the first occurrence of a substring in a string.
//
// 返回 needle 在 haystack 中首次出现的数字位置。
func Pos(haystack, needle string, startOffset...int) int {
length := len(haystack)
offset := 0
if len(startOffset) > 0 {
offset = startOffset[0]
}
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.
//
// 返回在字符串 haystack 中 needle 首次出现的数字位置(不区分大小写)。
func PosI(haystack, needle string, startOffset...int) int {
length := len(haystack)
offset := 0
if len(startOffset) > 0 {
offset = startOffset[0]
}
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, startOffset...int) int {
offset := 0
if len(startOffset) > 0 {
offset = startOffset[0]
}
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, startOffset...int) int {
offset := 0
if len(startOffset) > 0 {
offset = startOffset[0]
}
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
}

View File

@ -0,0 +1,49 @@
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
}

View File

@ -0,0 +1,54 @@
package gstr
// Calculate the soundex key of a string.
// See http://php.net/manual/en/function.soundex.php.
//
// 计算字符串的SOUNDEX值SOUNDEX为由四个字符组成的代码以评估两个字符串的相似性。
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)
}

View File

@ -9,7 +9,6 @@
package gstr_test
import (
"strings"
"testing"
)
@ -32,17 +31,4 @@ func Benchmark_BytesToString(b *testing.B) {
}
}
}
func Benchmark_Strings_ToUpper(b *testing.B) {
for i := 0; i < b.N; i++ {
strings.ToUpper(str)
}
}
func Benchmark_Strings_ToLower(b *testing.B) {
for i := 0; i < b.N; i++ {
strings.ToLower(str)
}
}
}

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