From c617a139a5d19c2d6984177433a076c22cbf2dc2 Mon Sep 17 00:00:00 2001 From: CC11001100 Date: Wed, 8 Feb 2023 10:36:14 +0800 Subject: [PATCH 1/2] add todo --- fisher_yates_knuth_shuffle.go | 2 ++ sattolo.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/fisher_yates_knuth_shuffle.go b/fisher_yates_knuth_shuffle.go index b81b419..49d701c 100644 --- a/fisher_yates_knuth_shuffle.go +++ b/fisher_yates_knuth_shuffle.go @@ -18,6 +18,8 @@ func FisherYatesKnuthShuffle[T any](slice []T) { // FisherYatesShuffleMatrix Fisher–Yates-Knuth shuffle算法对矩阵洗牌 func FisherYatesShuffleMatrix[T any](matrix [][]T) { + // TODO 检查参数是否符合矩阵 + // TODO 提高性能 row, col := len(matrix), len(matrix[0]) for index := row*col - 1; index > 0; index-- { chooseIndex := standaloneRand.Intn(index + 1) diff --git a/sattolo.go b/sattolo.go index 4a4727a..608eaaa 100644 --- a/sattolo.go +++ b/sattolo.go @@ -11,6 +11,8 @@ func SattoloShuffle[T any](slice []T) { func SattoloShuffleMatrix[T any](matrix [][]T) { row, col := len(matrix), len(matrix[0]) for index := row*col - 1; index > 0; index-- { + // TODO 检查参数是否符合矩阵 + // TODO 提高性能 chooseIndex := standaloneRand.Intn(index) matrix[index/col][index%col], matrix[chooseIndex/col][chooseIndex%col] = matrix[chooseIndex/col][chooseIndex%col], matrix[index/col][index%col] } From 0d21ce80c35b24f43f14fd8ee309b7faa3123043 Mon Sep 17 00:00:00 2001 From: CC11001100 Date: Wed, 8 Feb 2023 11:08:30 +0800 Subject: [PATCH 2/2] add matrix check & examples & doc --- README.md | 108 +++++++++++++++++++++++---- errors.go | 5 ++ examples/shuffle_matrix/main.go | 27 +++++++ examples/{ => shuffle_slice}/main.go | 3 +- fisher_yates_knuth_shuffle.go | 22 +++++- fisher_yates_knuth_shuffle_test.go | 8 +- go.mod | 8 ++ go.sum | 17 +++++ sattolo.go | 12 ++- shuffle.go | 4 +- 10 files changed, 189 insertions(+), 25 deletions(-) create mode 100644 errors.go create mode 100644 examples/shuffle_matrix/main.go rename examples/{ => shuffle_slice}/main.go (75%) create mode 100644 go.sum diff --git a/README.md b/README.md index 3f3c685..2db0c60 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 各种洗牌算法的Go语言实现 +# 洗牌算法(Shuffle Algorithm) # 一、支持的洗牌算法 @@ -15,17 +15,9 @@ go get -u github.com/golang-infrastructure/go-shuffle ``` -# 三、Fisher–Yates-Knuth洗牌算法 +# 三、API代码示例 -假设现在有一个数组: - -``` -[1, 2, 3, 4, 5] -``` - -从最右边的坐标`len(slice)-1`开始作为`right_index`,每次从`[0, right_index]`随机选择一个下标,将选中的下标的值与`right_index`交换,并将`right_index`减一往左偏移。 - -代码示例: +## 3.1 对切片shuffle ```go package main @@ -37,8 +29,9 @@ import ( func main() { + // 对切片中的元素shuffle slice := []int{1, 2, 3, 4, 5} - shuffle.FisherYatesKnuthShuffle(slice) + shuffle.Shuffle(slice) fmt.Println(slice) // Output: // [5 1 2 3 4] @@ -46,7 +39,94 @@ func main() { } ``` -# 四、Scatology算法 +## 3.2 对矩阵shuffle + +```go +package main + +import ( + "fmt" + "github.com/golang-infrastructure/go-shuffle" +) + +func main() { + + // 对二维矩阵中的元素shuffle + matrix := [][]int{ + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + {11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, + {21, 22, 23, 24, 25, 26, 27, 28, 29, 30}, + {31, 32, 33, 34, 35, 36, 37, 38, 39, 40}, + } + // 注意可能会返回错误,比如二维数组每行长度不一致则无法shuffle + err := shuffle.ShuffleMatrix(matrix) + if err != nil { + fmt.Println("Shuffle matrix failed: " + err.Error()) + return + } + fmt.Println(matrix) + // Output: + // [[11 40 6 23 15 28 4 7 37 21] [29 26 33 5 35 13 22 32 19 34] [31 30 36 20 2 10 24 39 9 27] [16 8 18 14 1 17 38 12 25 3]] + +} +``` + +# 四、Fisher–Yates-Knuth洗牌算法 + +假设现在有一个数组: + +``` +[1, 2, 3, 4, 5] +``` + +从最右边的坐标`len(slice)-1`开始作为`right_index`,每次从`[0, right_index]`随机选择一个下标,将选中的下标的值与`right_index`交换,并将`right_index`减一往左偏移。 + +代码示例: + +```go +// 使用自己独立的随机数生成器,与其它的调用区分开 +var standaloneRand = rand.New(rand.NewSource(time.Now().Unix())) + +// FisherYatesKnuthShuffle Fisher–Yates-Knuth Shuffle或 算法对一维数组洗牌,O(n) +func FisherYatesKnuthShuffle[T any](slice []T) { + for index := len(slice) - 1; index > 0; index-- { + chooseIndex := standaloneRand.Intn(index + 1) + slice[chooseIndex], slice[index] = slice[index], slice[chooseIndex] + } +} +``` + +我们对上面的算法扩展一下,很容易就能得到矩阵的shuffle算法,将矩阵的每一行看做是拼接起来的一维数组,则将对矩阵进行shuffle的算法转换为了对切片shufle的算法,而对切片进行shuffle我们已经实现过了,API代码示例: + +```go +// FisherYatesShuffleMatrix Fisher–Yates-Knuth shuffle算法对矩阵洗牌 +func FisherYatesShuffleMatrix[T any](matrix [][]T) error { + + // 参数检查 + if err := check(matrix); err != nil { + return err + } + + row, col := len(matrix), len(matrix[0]) + for index := row*col - 1; index > 0; index-- { + chooseIndex := standaloneRand.Intn(index + 1) + matrix[index/col][index%col], matrix[chooseIndex/col][chooseIndex%col] = matrix[chooseIndex/col][chooseIndex%col], matrix[index/col][index%col] + } + + return nil +} + +// 需要保证传入的二维数据是一个矩阵,否则后面可能会越界panic +func check[T any](matrix [][]T) error { + for i := 1; i < len(matrix); i++ { + if len(matrix[i]) != len(matrix[i-1]) { + return ErrMatrixUnavailable + } + } + return nil +} +``` -就是在Fisher–Yates-Knuth的基础上随机选择的时候不再选择最右边的`[0,right_index)`,但是感觉这样子似乎可能会有概率问题? +# 五、Scatology算法 +就是在Fisher–Yates-Knuth的基础上随机选择的时候不再选择最右边的`[0,right_index)`,不再展开详解。 diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..4e51d6c --- /dev/null +++ b/errors.go @@ -0,0 +1,5 @@ +package shuffle + +import "errors" + +var ErrMatrixUnavailable = errors.New("matrix unavailable") diff --git a/examples/shuffle_matrix/main.go b/examples/shuffle_matrix/main.go new file mode 100644 index 0000000..b376e43 --- /dev/null +++ b/examples/shuffle_matrix/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/golang-infrastructure/go-shuffle" +) + +func main() { + + // 对二维矩阵中的元素shuffle + matrix := [][]int{ + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + {11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, + {21, 22, 23, 24, 25, 26, 27, 28, 29, 30}, + {31, 32, 33, 34, 35, 36, 37, 38, 39, 40}, + } + // 注意可能会返回错误,比如二维数组每行长度不一致则无法shuffle + err := shuffle.ShuffleMatrix(matrix) + if err != nil { + fmt.Println("Shuffle matrix failed: " + err.Error()) + return + } + fmt.Println(matrix) + // Output: + // [[11 40 6 23 15 28 4 7 37 21] [29 26 33 5 35 13 22 32 19 34] [31 30 36 20 2 10 24 39 9 27] [16 8 18 14 1 17 38 12 25 3]] + +} diff --git a/examples/main.go b/examples/shuffle_slice/main.go similarity index 75% rename from examples/main.go rename to examples/shuffle_slice/main.go index 1e9f413..d8e75a6 100644 --- a/examples/main.go +++ b/examples/shuffle_slice/main.go @@ -7,8 +7,9 @@ import ( func main() { + // 对切片中的元素shuffle slice := []int{1, 2, 3, 4, 5} - shuffle.FisherYatesKnuthShuffle(slice) + shuffle.Shuffle(slice) fmt.Println(slice) // Output: // [5 1 2 3 4] diff --git a/fisher_yates_knuth_shuffle.go b/fisher_yates_knuth_shuffle.go index 49d701c..aa13bb5 100644 --- a/fisher_yates_knuth_shuffle.go +++ b/fisher_yates_knuth_shuffle.go @@ -17,12 +17,28 @@ func FisherYatesKnuthShuffle[T any](slice []T) { } // FisherYatesShuffleMatrix Fisher–Yates-Knuth shuffle算法对矩阵洗牌 -func FisherYatesShuffleMatrix[T any](matrix [][]T) { - // TODO 检查参数是否符合矩阵 - // TODO 提高性能 +func FisherYatesShuffleMatrix[T any](matrix [][]T) error { + + // 参数检查 + if err := check(matrix); err != nil { + return err + } + row, col := len(matrix), len(matrix[0]) for index := row*col - 1; index > 0; index-- { chooseIndex := standaloneRand.Intn(index + 1) matrix[index/col][index%col], matrix[chooseIndex/col][chooseIndex%col] = matrix[chooseIndex/col][chooseIndex%col], matrix[index/col][index%col] } + + return nil +} + +// 需要保证传入的二维数据是一个矩阵,否则后面可能会越界panic +func check[T any](matrix [][]T) error { + for i := 1; i < len(matrix); i++ { + if len(matrix[i]) != len(matrix[i-1]) { + return ErrMatrixUnavailable + } + } + return nil } diff --git a/fisher_yates_knuth_shuffle_test.go b/fisher_yates_knuth_shuffle_test.go index d4d6f8f..bd9f7a3 100644 --- a/fisher_yates_knuth_shuffle_test.go +++ b/fisher_yates_knuth_shuffle_test.go @@ -1,6 +1,9 @@ package shuffle -import "testing" +import ( + "github.com/stretchr/testify/assert" + "testing" +) func TestFisherYatesShuffle(t *testing.T) { slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} @@ -15,6 +18,7 @@ func TestFisherYatesShuffleMatrix(t *testing.T) { {21, 22, 23, 24, 25, 26, 27, 28, 29, 30}, {31, 32, 33, 34, 35, 36, 37, 38, 39, 40}, } - FisherYatesShuffleMatrix(matrix) + err := FisherYatesShuffleMatrix(matrix) + assert.Nil(t, err) t.Log(matrix) } diff --git a/go.mod b/go.mod index 450ce4c..0360290 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module github.com/golang-infrastructure/go-shuffle go 1.18 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d9320db --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sattolo.go b/sattolo.go index 608eaaa..7fc2473 100644 --- a/sattolo.go +++ b/sattolo.go @@ -8,12 +8,18 @@ func SattoloShuffle[T any](slice []T) { } } -func SattoloShuffleMatrix[T any](matrix [][]T) { +func SattoloShuffleMatrix[T any](matrix [][]T) error { + + // 参数检查 + if err := check(matrix); err != nil { + return err + } + row, col := len(matrix), len(matrix[0]) for index := row*col - 1; index > 0; index-- { - // TODO 检查参数是否符合矩阵 - // TODO 提高性能 chooseIndex := standaloneRand.Intn(index) matrix[index/col][index%col], matrix[chooseIndex/col][chooseIndex%col] = matrix[chooseIndex/col][chooseIndex%col], matrix[index/col][index%col] } + + return nil } diff --git a/shuffle.go b/shuffle.go index 5dce86b..46b63e2 100644 --- a/shuffle.go +++ b/shuffle.go @@ -6,6 +6,6 @@ func Shuffle[T any](slice []T) { } // ShuffleMatrix 对矩阵中的元素进行洗牌 -func ShuffleMatrix[T any](matrix [][]T) { - FisherYatesShuffleMatrix(matrix) +func ShuffleMatrix[T any](matrix [][]T) error { + return FisherYatesShuffleMatrix(matrix) }