英文:
Golang Cryptographic Shuffle
问题
我正在尝试在Go语言中实现一个使用crypto/rand而不是math/rand的字符串洗牌函数。Fisher-Yates Shuffle需要随机整数,所以我尝试实现该功能,而不必使用依赖于math/big的crypto/rand Int。以下是我目前想出的最佳方法,但是否有更好的方法呢?我找不到现有的示例,这让我想知道为什么没有人这样做的好原因!
package main
import "crypto/rand"
import "fmt"
import "encoding/binary"
func randomInt(max int) int {
var n uint16
binary.Read(rand.Reader, binary.LittleEndian, &n)
return int(n) % max
}
func shuffle(s *[]string) {
slice := *s
for i := range slice {
j := randomInt(i + 1)
slice[i], slice[j] = slice[j], slice[i]
}
*s = slice
}
func main() {
slice := []string{"a", "b", "c", "d", "e", "f", "h", "i", "j", "k"}
shuffle(&slice)
fmt.Println(slice)
}
英文:
I'm trying to implement a string shuffle function in Go that uses crypto/rand instead of math/rand. The Fisher-Yates Shuffle requires random integers so I've tried to implement that functionality, without having to use crypto/rand Int which relies on math/big. Below is the best I've come up with so far but is there a better method? The fact that I can't find existing examples leads me to wonder if there's a good reason why nobody does this!
package main
import "crypto/rand"
import "fmt"
import "encoding/binary"
func randomInt(max int) int {
var n uint16
binary.Read(rand.Reader, binary.LittleEndian, &n)
return int(n) % max
}
func shuffle(s *[]string) {
slice := *s
for i := range slice {
j := randomInt(i + 1)
slice[i], slice[j] = slice[j], slice[i]
}
*s = slice
}
func main() {
slice := []string{"a", "b", "c", "d", "e", "f", "h", "i", "j", "k"}
shuffle(&slice)
fmt.Println(slice)
}
2: https://golang.org/pkg/crypto/rand/#Int "rand Int"
3: https://golang.org/pkg/math/big/
答案1
得分: 9
Go的math/rand
库提供了很好的功能,可以从Source
生成随机数值原语。
// Source表示在范围[0, 1<<63)内生成均匀分布的伪随机int64值的源。
type Source interface {
Int63() int64
Seed(seed int64)
}
NewSource(seed int64)
返回内置的确定性伪随机数生成器,但是New(source Source)
可以接受满足Source
接口的任何东西。
下面是一个由crypto/rand
支持的Source
示例。
type CryptoRandSource struct{}
func NewCryptoRandSource() CryptoRandSource {
return CryptoRandSource{}
}
func (_ CryptoRandSource) Int63() int64 {
var b [8]byte
rand.Read(b[:])
// 掩码掉符号位以确保为正数
return int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1))
}
func (_ CryptoRandSource) Seed(_ int64) {}
你可以像这样使用它:
r := rand.New(NewCryptoRandSource())
for i := 0; i < 10; i++ {
fmt.Println(r.Int())
}
math/rand
库还有一个正确实现的Intn()
方法,可以确保均匀分布。
func (r *Rand) Intn(n int) int {
if n <= 0 {
panic("invalid argument to Intn")
}
if n <= 1<<31-1 {
return int(r.Int31n(int32(n)))
}
return int(r.Int63n(int64(n)))
}
func (r *Rand) Int31n(n int32) int32 {
if n <= 0 {
panic("invalid argument to Int31n")
}
if n&(n-1) == 0 { // n是2的幂,可以进行掩码
return r.Int31() & (n - 1)
}
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
v := r.Int31()
for v > max {
v = r.Int31()
}
return v % n
}
func (r *Rand) Int63n(n int64) int64 {
if n <= 0 {
panic("invalid argument to Int63n")
}
if n&(n-1) == 0 { // n是2的幂,可以进行掩码
return r.Int63() & (n - 1)
}
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
v := r.Int63()
for v > max {
v = r.Int63()
}
return v % n
}
还可以将密码哈希函数包装为Source
,以实现替代的随机性手段。
英文:
Go's math/rand
library has good facilities for producing random numerical primitives from a Source
.
// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1<<63).
type Source interface {
Int63() int64
Seed(seed int64)
}
NewSource(seed int64)
returns the builtin, deterministic PRNG, but New(source Source)
will allow anything that satisfies the Source
interface.
Here is an example of a Source
that is backed by crypto/rand
.
type CryptoRandSource struct{}
func NewCryptoRandSource() CryptoRandSource {
return CryptoRandSource{}
}
func (_ CryptoRandSource) Int63() int64 {
var b [8]byte
rand.Read(b[:])
// mask off sign bit to ensure positive number
return int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1))
}
func (_ CryptoRandSource) Seed(_ int64) {}
You can use it like this:
r := rand.New(NewCryptoRandSource())
for i := 0; i < 10; i++ {
fmt.Println(r.Int())
}
The math/rand
library has a properly implemented Intn()
method which ensures a uniform distribution.
func (r *Rand) Intn(n int) int {
if n <= 0 {
panic("invalid argument to Intn")
}
if n <= 1<<31-1 {
return int(r.Int31n(int32(n)))
}
return int(r.Int63n(int64(n)))
}
func (r *Rand) Int31n(n int32) int32 {
if n <= 0 {
panic("invalid argument to Int31n")
}
if n&(n-1) == 0 { // n is power of two, can mask
return r.Int31() & (n - 1)
}
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
v := r.Int31()
for v > max {
v = r.Int31()
}
return v % n
}
func (r *Rand) Int63n(n int64) int64 {
if n <= 0 {
panic("invalid argument to Int63n")
}
if n&(n-1) == 0 { // n is power of two, can mask
return r.Int63() & (n - 1)
}
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
v := r.Int63()
for v > max {
v = r.Int63()
}
return v % n
}
Cryptographic hash functions also can be wrapped as a Source
for alternate means of randomness.
答案2
得分: 0
从n%max
得到的数字并不均匀分布。例如,
package main
import (
"fmt"
"math"
)
func main() {
max := 7
size := math.MaxUint8
count := make([]int, size)
for i := 0; i < size; i++ {
count[i%max]++
}
fmt.Println(count[:max])
}
输出结果:
[37 37 37 36 36 36 36]
英文:
The numbers from n % max
are not distributed uniformly. For example,
package main
import (
"fmt"
"math"
)
func main() {
max := 7
size := math.MaxUint8
count := make([]int, size)
for i := 0; i < size; i++ {
count[i%max]++
}
fmt.Println(count[:max])
}
Output:
[37 37 37 36 36 36 36]
答案3
得分: -1
根据收到的评论,我认为我可以改进我问题中的示例,通过添加一个uniformInt函数,用uint32填充而不是uint16,并删除对切片的指针引用。
package main
import "crypto/rand"
import "fmt"
import "encoding/binary"
func randomInt() int {
var n uint32
binary.Read(rand.Reader, binary.LittleEndian, &n)
return int(n)
}
func uniformInt(max int) (r int) {
divisor := 4294967295 / max // Max Uint32
for {
r = randomInt() / divisor
if r <= max {
break
}
}
return
}
func shuffle(slice []string) {
for i := range slice {
j := uniformInt(i + 1)
slice[i], slice[j] = slice[j], slice[i]
}
}
func main() {
slice := []string{"a", "b", "c", "d", "e", "f", "h", "i", "j", "k"}
shuffle(slice)
fmt.Println(slice)
}
英文:
Based on the comments received, I think I can improve on the example in my question by adding a uniformInt function, populating a uint32 instead of a uint16 and removing the pointer to the slice.
package main
import "crypto/rand"
import "fmt"
import "encoding/binary"
func randomInt() int {
var n uint32
binary.Read(rand.Reader, binary.LittleEndian, &n)
return int(n)
}
func uniformInt(max int) (r int) {
divisor := 4294967295 / max // Max Uint32
for {
r = randomInt() / divisor
if r <= max {
break
}
}
return
}
func shuffle(slice []string) {
for i := range slice {
j := uniformInt(i + 1)
slice[i], slice[j] = slice[j], slice[i]
}
}
func main() {
slice := []string{"a", "b", "c", "d", "e", "f", "h", "i", "j", "k"}
shuffle(slice)
fmt.Println(slice)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论