跳到主要内容
版本:2.10.x(Latest)

约瑟夫问题

我们使用 gring 来模拟一下 约瑟夫问题

信息

著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。

然而Josephus和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?

传统方式实现

以下示例为非并发安全场景。

package main

import (
"fmt"
"github.com/gogf/gf/v2/container/gring"
)

type Player struct {
position int // 位置
alive bool // 是否存活
}

const (
playerCount = 41 // 玩家人数
startPos = 1 // 开始报数位置
)

var (
deadline = 3
)

func main() {
r := gring.New(playerCount)

// 设置所有玩家初始值
for i := 1; i <= playerCount; i++ {
r.Put(&Player{i, true})
}

// 如果开始报数的位置不为1,则设置开始位置
if startPos > 1 {
r.Move(startPos - 1)
}

counter := 1 // 报数从1开始,因为下面的循环从第二个开始计算
deadCount := 0 // 死亡人数,初始值为0

// 直到所有人都死亡,否则循环一直执行
for deadCount < playerCount {
// 跳到下一个人
r.Next()

// 如果是活着的人,则报数
if r.Val().(*Player).alive {
counter++
}

// 如果报数为deadline,则此人淘汰出局
if counter == deadline {
r.Val().(*Player).alive = false
fmt.Printf("Player %d died!\n", r.Val().(*Player).position)
deadCount++
counter = 0
}
}
}

执行后,输出结果为:

Player 3 died!
Player 6 died!
Player 9 died!
Player 12 died!
Player 15 died!
Player 18 died!
Player 21 died!
Player 24 died!
Player 27 died!
Player 30 died!
Player 33 died!
Player 36 died!
Player 39 died!
Player 1 died!
Player 5 died!
Player 10 died!
Player 14 died!
Player 19 died!
Player 23 died!
Player 28 died!
Player 32 died!
Player 37 died!
Player 41 died!
Player 7 died!
Player 13 died!
Player 20 died!
Player 26 died!
Player 34 died!
Player 40 died!
Player 11 died!
Player 22 died!
Player 35 died!
Player 8 died!
Player 25 died!
Player 4 died!
Player 29 died!
Player 2 died!
Player 38 died!
Player 17 died!
Player 12 died!
Player 31 died!

泛型版本实现

提示

版本要求:v2.10.0

v2.10.0 版本开始,可以使用泛型版本的 TRing[T] 实现相同功能,代码更简洁且类型安全。

约瑟夫问题泛型实现

package main

import (
"fmt"
"github.com/gogf/gf/v2/container/gring"
)

type Player struct {
Position int // 位置
Alive bool // 是否存活
}

const (
playerCount = 41 // 玩家人数
startPos = 1 // 开始报数位置
deadline = 3 // 报数阈值
)

func main() {
// 创建泛型环,类型安全
r := gring.NewTRing[*Player](playerCount)

// 设置所有玩家初始值
for i := 1; i <= playerCount; i++ {
r.Put(&Player{
Position: i,
Alive: true,
})
}

// 如果开始报数的位置不为1,则设置开始位置
if startPos > 1 {
r.Move(startPos - 1)
}

counter := 1 // 报数从1开始
deadCount := 0 // 死亡人数

// 直到所有人都死亡,否则循环一直执行
for deadCount < playerCount {
// 跳到下一个人
r.Next()

// 无需类型断言,直接使用
player := r.Val()

// 如果是活着的人,则报数
if player.Alive {
counter++
}

// 如果报数为deadline,则此人淘汰出局
if counter == deadline {
player.Alive = false
fmt.Printf("Player %d died!\n", player.Position)
deadCount++
counter = 0
}
}
}

循环缓冲区示例

使用泛型环实现固定大小的循环缓冲区:

package main

import (
"fmt"
"github.com/gogf/gf/v2/container/gring"
)

type LogEntry struct {
Timestamp int64
Message string
Level string
}

func main() {
// 创建容量为 5 的日志缓冲环
logBuffer := gring.NewTRing[*LogEntry](5)

// 添加 10 条日志(超过容量,旧的会被覆盖)
for i := 1; i <= 10; i++ {
logBuffer.Put(&LogEntry{
Timestamp: int64(i),
Message: fmt.Sprintf("Log message %d", i),
Level: "INFO",
})
}

fmt.Printf("Buffer capacity: %d\n", logBuffer.Cap())
fmt.Printf("Buffer length: %d\n", logBuffer.Len())

// 获取所有日志(只有最新的5条)
logBuffer.Move(-5)
logBuffer.RLockIteratorNext(func(entry *LogEntry) bool {
fmt.Printf("Timestamp: %d, Level: %s, Message: %s\n",
entry.Timestamp, entry.Level, entry.Message)
return true
})
}

执行后,输出结果为:

Buffer capacity: 5
Buffer length: 5
Timestamp: 6, Level: INFO, Message: Log message 6
Timestamp: 7, Level: INFO, Message: Log message 7
Timestamp: 8, Level: INFO, Message: Log message 8
Timestamp: 9, Level: INFO, Message: Log message 9
Timestamp: 10, Level: INFO, Message: Log message 10

滑动窗口统计示例

使用泛型环实现滑动窗口统计:

package main

import (
"fmt"
"github.com/gogf/gf/v2/container/gring"
)

func main() {
// 创建一个窗口大小为 5 的统计环
window := gring.NewTRing[int](5)

// 模拟数据流
dataStream := []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}

for _, value := range dataStream {
// 添加新数据
window.Put(value)

// 计算当前窗口的和
sum := 0
values := window.SliceNext()
for _, v := range values {
sum += v
}

// 计算平均值
count := len(values)
if count > 0 {
avg := float64(sum) / float64(count)
fmt.Printf("Value: %d, Window: %v, Sum: %d, Avg: %.2f\n",
value, values, sum, avg)
}
}
}

执行后,输出结果为:

Value: 10, Window: [10], Sum: 10, Avg: 10.00
Value: 20, Window: [10 20], Sum: 30, Avg: 15.00
Value: 30, Window: [10 20 30], Sum: 60, Avg: 20.00
Value: 40, Window: [10 20 30 40], Sum: 100, Avg: 25.00
Value: 50, Window: [10 20 30 40 50], Sum: 150, Avg: 30.00
Value: 60, Window: [20 30 40 50 60], Sum: 200, Avg: 40.00
Value: 70, Window: [30 40 50 60 70], Sum: 250, Avg: 50.00
Value: 80, Window: [40 50 60 70 80], Sum: 300, Avg: 60.00
Value: 90, Window: [50 60 70 80 90], Sum: 350, Avg: 70.00
Value: 100, Window: [60 70 80 90 100], Sum: 400, Avg: 80.00

并发安全使用

并发写入示例

package main

import (
"fmt"
"sync"
"time"
"github.com/gogf/gf/v2/container/gring"
)

type Task struct {
ID int
Name string
CreatedAt time.Time
}

func main() {
// 创建并发安全的任务环
taskRing := gring.NewTRing[*Task](10, true)

var wg sync.WaitGroup

// 并发添加任务
for i := 0; i < 20; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
taskRing.Put(&Task{
ID: id,
Name: fmt.Sprintf("Task-%d", id),
CreatedAt: time.Now(),
})
}(i)
}

wg.Wait()

// 读取所有任务
fmt.Printf("Total tasks in ring: %d\n", taskRing.Len())
taskRing.RLockIteratorNext(func(task *Task) bool {
fmt.Printf("Task ID: %d, Name: %s\n", task.ID, task.Name)
return true
})
}

类型安全对比

传统方式 vs 泛型方式

package main

import (
"fmt"
"github.com/gogf/gf/v2/container/gring"
)

type User struct {
ID int
Name string
}

func main() {
// 传统方式 - 需要类型断言
r1 := gring.New(5)
r1.Put(&User{ID: 1, Name: "Alice"})

// 需要类型断言,运行时可能 panic
user1 := r1.Val().(*User)
fmt.Printf("User1: %+v\n", user1)

// 泛型方式 - 类型安全
r2 := gring.NewTRing[*User](5)
r2.Put(&User{ID: 2, Name: "Bob"})

// 无需类型断言,编译时检查
user2 := r2.Val()
fmt.Printf("User2: %+v\n", user2)
}

性能说明

  • 泛型版本 TRing[T] 与传统版本 Ring 性能基本一致
  • 泛型版本在编译时进行类型检查,避免了运行时的类型断言开销
  • 环形结构使用固定内存,不会产生额外的内存分配
  • 推荐在新项目中使用泛型版本,提升代码安全性和可维护性