Skip to content

Commit

Permalink
refactor: 支持 rune 类型
Browse files Browse the repository at this point in the history
  • Loading branch information
caixw committed Mar 22, 2024
1 parent 2301f9a commit aa426fd
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 71 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ rands

rands 为一个随机字符串生成工具。

从 v3 开始只支持 go1.22 以之后的版本
*从 v3 开始只支持 go1.22 以之后的版本*

```go
// 生成一个长度为 [8,10) 之间的随机字符串
str := rands.String(8, 10, []byte("1234567890abcdefg"))
// 拿 [8,10) 数量的 rune,每个汉字都是完整的。
str := rands.String(8, 10, []rune("rands 为一个随机字符串生成工具"))

// 拿 [8,10) 数量的 byte,汉字可能会被截断。
str := rands.String(8, 10, []byte("rands 为一个随机字符串生成工具"))


// 生成一个带缓存功能的随机字符串生成器
Expand Down
28 changes: 22 additions & 6 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,17 @@ import (
)

func BenchmarkString(b *testing.B) {
for i := 0; i < b.N; i++ {
String(6, 7, []byte("abcdefghijklmnopkrstuvwxyz"))
}
b.Run("bytes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
String(6, 7, []byte("abcdefghijklmnopkrstuvwxyz"))
}
})

b.Run("runes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
String(6, 7, []rune("中文内容也可以正常显示"))
}
})
}

// 固定长度的随机字符串
Expand Down Expand Up @@ -48,9 +56,17 @@ func BenchmarkBytes_10_32_Lower(b *testing.B) {
}

func BenchmarkBytes_10_32_All(b *testing.B) {
for i := 0; i < b.N; i++ {
Bytes(10, 32, AlphaNumberPunct())
}
b.Run("bytes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Bytes(10, 32, AlphaNumberPunct())
}
})

b.Run("runes", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Bytes(10, 32, []rune("中文内容也可以正常显示中文内容也可以正常显示"))
}
})
}

// crypto/rand包的随机读取能力
Expand Down
35 changes: 35 additions & 0 deletions chars_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2024 caixw
//
// SPDX-License-Identifier: MIT

package rands

import (
"testing"

"github.com/issue9/assert/v4"
)

func TestChars(t *testing.T) {
a := assert.New(t, false)

s := Alpha()
a.Equal(s[0], 'a').
Equal(s[len(s)-1], 'Z')

s = Number()
a.Equal(s[0], '1').
Equal(s[len(s)-1], '0')

s = Punct()
a.Equal(s[0], '!').
Equal(s[len(s)-1], '?')

s = AlphaNumber()
a.Equal(s[0], 'a').
Equal(s[len(s)-1], '0')

s = AlphaNumberPunct()
a.Equal(s[0], 'a').
Equal(s[len(s)-1], '?')
}
87 changes: 60 additions & 27 deletions rands.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
// Package rands 生成各种随机字符串
//
// // 生成一个长度介于 [6,9) 之间的随机字符串
// str := rands.String(6, 9, "1343567")
// str := rands.String(6, 9, []byte("1343567"))
//
// // 生成一个带缓存功能的随机字符串生成器
// r := rands.New(nil, 100, 5, 7, "adbcdefgadf;dfe1334")
// r := rands.New(nil, 100, 5, 7, []byte("adbcdefgadf;dfe1334"))
// ctx,cancel := context.WithCancel(context.Background())
// go r.Serve(ctx)
// defer cancel()
Expand All @@ -19,38 +19,48 @@
package rands

import (
"bytes"
"context"
"math/rand/v2"
"unsafe"
)

// Bytes 产生随机字符数组
// Char 约束随机字符串字符的类型
//
// byte 的性能为好于 rune。
type Char interface{ ~byte | ~rune }

// Bytes 从 bs 中随机抓取 [min,max) 个字符并返回
//
// 其长度为[min, max),bs 所有的随机字符串从此处取
func Bytes(min, max int, bs []byte) []byte {
// NOTE: bs 的类型可以是 rune,但是返回类型始终是 []byte,所以用 len 判断返回值可能其值会很大
func Bytes[T Char](min, max int, bs []T) []byte {
checkArgs(min, max, bs)
return bytes(rand.IntN, rand.Uint64, min, max, bs)
return gen(rand.IntN, rand.Uint64, min, max, bs)
}

// String 产生一个随机字符串
//
// 其长度为[min, max),bs 可用的随机字符。
func String(min, max int, bs []byte) string { return string(Bytes(min, max, bs)) }
func String[T Char](min, max int, bs []T) string {
cs := Bytes(min, max, bs)
return unsafe.String(unsafe.SliceData(cs), len(cs))
}

// Rands 提供随机字符串的生成
type Rands struct {
type Rands[T Char] struct {
intn func(int) int
u64 func() uint64

min, max int
bytes []byte
bytes []T
channel chan []byte
}

// New 声明 [Rands]
//
// 如果 r 为 nil,将采用默认的随机函数;
// bufferSize 缓存的随机字符串数量,若为 0,表示不缓存;
func New(r *rand.Rand, bufferSize, min, max int, bs []byte) *Rands {
func New[T Char](r *rand.Rand, bufferSize, min, max int, bs []T) *Rands[T] {
checkArgs(min, max, bs)

if bufferSize <= 0 {
Expand All @@ -67,7 +77,7 @@ func New(r *rand.Rand, bufferSize, min, max int, bs []byte) *Rands {
u64 = rand.Uint64
}

return &Rands{
return &Rands[T]{
intn: intn,
u64: u64,

Expand All @@ -81,26 +91,32 @@ func New(r *rand.Rand, bufferSize, min, max int, bs []byte) *Rands {
// Bytes 产生随机字符数组
//
// 功能与全局函数 [Bytes] 相同,但参数通过 [New] 预先指定。
func (r *Rands) Bytes() []byte { return <-r.channel }
func (r *Rands[T]) Bytes() []byte { return <-r.channel }

// String 产生一个随机字符串
//
// 功能与全局函数 [String] 相同,但参数通过 [New] 预先指定。
func (r *Rands) String() string { return string(<-r.channel) }
func (r *Rands[T]) String() string {
cs := r.Bytes()
return unsafe.String(unsafe.SliceData(cs), len(cs))
}

func (r *Rands) Serve(ctx context.Context) error {
func (r *Rands[T]) Serve(ctx context.Context) error {
for {
select {
case <-ctx.Done():
close(r.channel)
return ctx.Err()
case r.channel <- bytes(r.intn, r.u64, r.min, r.max, r.bytes):
case r.channel <- gen(r.intn, r.u64, r.min, r.max, r.bytes):
}
}
}

// 生成介于 [min,max) 长度的随机字符数组
func bytes(intN func(int) int, u64 func() uint64, min, max int, bs []byte) []byte {
//
// intN 用于生成一个介于 [min, max) 之间的数据;
// u64 用生成一个 uint64 的随机数;
func gen[T Char](intN func(int) int, u64 func() uint64, min, max int, bs []T) []byte {
var l int
if max-1 == min {
l = min
Expand All @@ -122,23 +138,40 @@ func bytes(intN func(int) int, u64 func() uint64, min, max int, bs []byte) []byt
}
}

ret := make([]byte, l)
for i := 0; i < l; {
// 在 index 不够大时,index>>bit 可能会让 index 变为 0,为 0 的 index 应该抛弃。
for index := u64(); index > 0 && i < l; {
if idx := index & mask; idx < ll {
ret[i] = bs[idx]
i++
if _, isRune := any(bs).([]rune); !isRune {
ret := make([]byte, l)
for i := 0; i < l; { // 循环内会增加 i 的值。比直接使用 for i:= range l 要快。
// 在 index 不够大时,index>>bit 可能会让 index 提早变为 0,为 0 的 index 应该抛弃。
for index := u64(); index > 0 && i < l; {
if idx := index & mask; idx < ll {
ret[i] = byte(bs[idx])
i++
}
index >>= bit
}
}
return ret
} else {
s := bytes.Buffer{}
s.Grow(2 * l)

for i := 0; i < l; { // 循环内会增加 i 的值。比直接使用 for i:= range l 要快。
// 在 index 不够大时,index>>bit 可能会让 index 提早变为 0,为 0 的 index 应该抛弃。
for index := u64(); index > 0 && i < l; {
if idx := index & mask; idx < ll {
s.WriteRune(rune(bs[idx]))
i++
}
index >>= bit
}
index >>= bit
}
}

return ret
return s.Bytes()
}
}

// 检测各个参数是否合法
func checkArgs(min, max int, bs []byte) {
func checkArgs[T Char](min, max int, bs []T) {
if min <= 0 {
panic("min 值必须大于 0")
}
Expand Down
57 changes: 22 additions & 35 deletions rands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,11 @@ import (
"math/rand/v2"
"testing"
"time"
"unicode/utf8"

"github.com/issue9/assert/v4"
)

func TestChars(t *testing.T) {
a := assert.New(t, false)

s := Alpha()
a.Equal(s[0], 'a').
Equal(s[len(s)-1], 'Z')

s = Number()
a.Equal(s[0], '1').
Equal(s[len(s)-1], '0')

s = Punct()
a.Equal(s[0], '!').
Equal(s[len(s)-1], '?')

s = AlphaNumber()
a.Equal(s[0], 'a').
Equal(s[len(s)-1], '0')

s = AlphaNumberPunct()
a.Equal(s[0], 'a').
Equal(s[len(s)-1], '?')
}

func TestCheckArgs(t *testing.T) {
a := assert.New(t, false)

Expand All @@ -58,32 +35,42 @@ func TestCheckArgs(t *testing.T) {
a.NotPanic(func() { checkArgs(5, 6, []byte("123")) })
}

// bytes
func TestBytes1(t *testing.T) {
func TestGen(t *testing.T) {
a := assert.New(t, false)

r1 := rand.IntN
r2 := rand.Uint64

a.NotEqual(bytes(r1, r2, 10, 11, []byte("1234123lks;df")), bytes(r1, r2, 10, 11, []byte("1234123lks;df")))
a.NotEqual(bytes(r1, r2, 10, 11, []byte("1234123lks;df")), bytes(r1, r2, 10, 11, []byte("1234123lks;df")))
a.NotEqual(bytes(r1, r2, 10, 11, []byte("1234123lks;df")), bytes(r1, r2, 10, 11, []byte("1234123lks;df")))

println("String:", String(10, 11, AlphaNumberPunct()))
a.NotEqual(gen(r1, r2, 10, 11, []byte("1234123lks;df")), gen(r1, r2, 10, 11, []byte("1234123lks;df")))
a.NotEqual(gen(r1, r2, 10, 11, []byte("1234123lks;df")), gen(r1, r2, 10, 11, []byte("1234123lks;df")))
a.NotEqual(gen(r1, r2, 10, 11, []byte("1234123lks;df")), gen(r1, r2, 10, 11, []byte("1234123lks;df")))
}

// Bytes
func TestBytes2(t *testing.T) {
// Chars
func TestBytes(t *testing.T) {
a := assert.New(t, false)

// 测试固定长度
a.Equal(len(Bytes(8, 9, []byte("1ks;dfp123;4j;ladj;fpoqwe"))), 8)
a.Equal(utf8.RuneCount(Bytes(8, 9, []rune("中文内容也可以正常显示"))), 8)

// 非固定长度
l := len(Bytes(8, 10, []byte("adf;wieqpwekwjerpq")))
a.True(l >= 8 && l <= 10)

}

func TestString(t *testing.T) {
a := assert.New(t, false)

// 查看是否输出完整的字符
val := String(10, 11, []rune("中文内容也d可e以正常显示abc"))
t.Log("这将显示一段随机的中英文:", val)
for _, r := range val {
a.True(utf8.ValidRune(r))
}
}

func TestRandsBuffer(t *testing.T) {
func TestRands(t *testing.T) {
a := assert.New(t, false)

a.PanicString(func() {
Expand Down

0 comments on commit aa426fd

Please sign in to comment.