洗牌算法
洗牌我们首先想到的是使用随机数,每次获取一个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个空格,多出来的部分用空格填充。-
表示左对齐,不加为右对齐。