洗牌算法的golang实现,顺便学习几个知识点

洗牌算法

洗牌我们首先想到的是使用随机数,每次获取一个1-54范围的随机数,直到所有的编号都被分配到,但是这有一个问题,就是随机数的产生可能有大量的重复,或者极端一点,某一个编号一直也获取不到,这样做显然是不合理的。

我们改进一下,从[0, 53]取一个数,去掉之后在剩下的数字中再取,直到取完。这种思想是合理的。但是实现需要加点技巧。直接看代码:

func shuffle(arr []int) {
    rand.Seed(time.Now().UnixNano())
    for i := 0; i < 54; i++ {
        arr[i] = i
    }
    for i := 53; i > 0; i-- {
        position := rand.Intn(i + 1) // [0,54) [0,53) [0,52) [0,51)... [0,2)
        arr[position], arr[i] = arr[i], arr[position]
    }
}

首先,我们将arr切片中每个位置添加对应牌号,0-53,然后我们在[0,54)中生成一个随机数,然后将其对应位置的值——牌号,和最后一个位置53的牌号互换,这就相当于是将数字取出了;然后下一个迭代,我们从[0, 53)中取随机数,它的位置的值与52位置的值互换;直到范围缩小到[0,1)停止循环。

从编号到花色

我们知道一副牌中,a desk of playing cards 有54张牌,其中除了两张大小王牌joker和black joker之外,其余每一种牌都有4个花色 heart spade club和diamond,分别对应ace 2 3 4 ... 10 jack queen king 共13种。我们想要通过卡牌的牌号0-53来打印出对应的花色应该怎么做呢?golang里面是没有枚举类型的,我们可以使用一个map来实现int和string的对应:

func showCard(n int) {
    suit := map[int]string{
        0: "heart",
        1: "spade",
        2: "\u9ED1\u6843", // 黑桃的Unicode码点
        3: "diamond",
    }
    num := map[int]string{
        0:  "A",
        1:  "2",
        2:  "3",
        3:  "4",
        4:  "5",
        5:  "6",
        6:  "7",
        7:  "8",
        8:  "9",
        9:  "10",
        10: "J",
        11: "Q",
        12: "K",
        13: "Joker1",
        14: "Joker2",
    }
    if n >= 52 {
        fmt.Printf("%-13s", num[n-39])
        return
    }
    fmt.Printf("%-2s %-10s", num[n/4], suit[n%4])
}

我们运行主程序看一下:

func main() {
    cards := make([]int, 54, 54)
    shuffle(cards)
    fmt.Println(cards)
    for i := 0; i < 54; i++ {
        if i%4 == 0 {
            fmt.Println()
        }
        showCard(i)
    }
}
[3 21 44 0 12 42 34 29 1 46 10 18 51 52 24 45 37 25 17 26 53 9 50 30 47 40 4 14 8 36 28 13 33 32 31 39 43 11 2 49 16 15 7 38 41 20 23 5 35 27 48 6 22 19]

A  heart     A  spade     A  黑桃        A  diamond
2  heart     2  spade     2  黑桃        2  diamond
3  heart     3  spade     3  黑桃        3  diamond
4  heart     4  spade     4  黑桃        4  diamond
5  heart     5  spade     5  黑桃        5  diamond
6  heart     6  spade     6  黑桃        6  diamond
7  heart     7  spade     7  黑桃        7  diamond
8  heart     8  spade     8  黑桃        8  diamond
9  heart     9  spade     9  黑桃        9  diamond
10 heart     10 spade     10 黑桃        10 diamond
J  heart     J  spade     J  黑桃        J  diamond
Q  heart     Q  spade     Q  黑桃        Q  diamond
K  heart     K  spade     K  黑桃        K  diamond
Joker1       Joker2

几个小知识

随机数生成

rand.Intn(int)会生成一个[0,n)的随机数,但是如果我们每次不修改随机数种子,他产生的随机数其实是固定顺序的

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    for i := 0; i < 8; i++ {
        fmt.Print(rand.Intn(5), " ")
    }
}

我们运行两次看一下结果:

E:\go\src\00test>go run main.go
1 2 2 4 1 3 0 0 
E:\go\src\00test>go run main.go
1 2 2 4 1 3 0 0 

因此,我们每次运行的时候要修改稿随机数种子,time.Now().UnixNano()方法会获取一个从时间戳到当前时间的纳秒数值,随时都在改变,可作为随机数种子。

// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
// since January 1, 1970 UTC. The result is undefined if the Unix time
// in nanoseconds cannot be represented by an int64 (a date before the year
// 1678 or after 2262). Note that this means the result of calling UnixNano
// on the zero Time is undefined. The result does not depend on the
// location associated with t.
func (t Time) UnixNano() int64 {
    return (t.unixSec())*1e9 + int64(t.nsec())
}

Unicode码点

Unicode( http://unicode.org ),它收集了这个世界上所有的符号系统,包括重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应Go语言中的rune整数类型(译注:rune是int32等价类型)。通用的表示一个Unicode码点的数据类型是int32,也就是Go语言中rune对应的类型;它的同义词rune符文正是这个意思。

Go语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。有两种形式:\uhhhh对应16bit的码点值,\Uhhhhhhhh对应32bit的码点值,其中h是一个十六进制数字;一般很少需要使用32bit的形式。每一个对应码点的UTF8编码。例如:下面的字母串面值都表示相同的值:

"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"

上面我们就通过黑桃的码点得到了黑桃汉字的string表示

格式化输出时设置每个字段占用空格数

fmt.Printf("%-2s %-10s", num[n/4], suit[n%4])

%-10s表示一个string类型的字段占用2个空格,多出来的部分用空格填充。-表示左对齐,不加为右对齐。

你可能感兴趣的:(洗牌算法的golang实现,顺便学习几个知识点)