稀疏数组——实现五子棋存盘和续上盘功能

1,898 阅读6分钟

本篇语言使用的是Golang,跟语言无关,主要目的是为了讲解稀疏数组的应用以及二维数组和稀疏数组之间的转化

1、基本介绍

当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。

稀疏数组的处理方法是:

  1. 记录数组一共有几行几列,有多少个不同的值

  2. 把具有不同值的元素的行列值记录在一个小规模的数组中,从而缩小程序的规模

下面通过一个五子棋存盘退出和续上盘的小栗子来实际运用和体会一下稀疏数组👇

2、五子棋游戏需求

编写的五子棋程序中,实现存盘退出续上盘的功能 image.png

2.1、分析问题:

因为该二维数组的很多值是默认值0,记录了很多没有意义的数据,因此采用稀疏数组过滤调没有意义的数据

  1. 存盘退出、续上盘

图片来源网络(侵删)

  1. 保存行和列:

    标准的稀疏数组,含有记录二维数组的规模

    上图直接把行列数据进行转化,没有记录总的行列数和默认值,下面增加对行列总数和默认值的描述👇

3、实现效果

以文本形式存储数据:

上面以文本格式的存储的行列值很直观,但是续上盘的时候处理起来需要一点技巧,

这里为了数据转化方便,我存放数据采用的是 map切片,(序列号存储map字符串切片可以直接反序列化成map)👇

// sparseArr 落盘数据
11	11	0
1	2	1
2	3	2

// sparseArrObj 序列化后落盘数据
[{"col":11,"row":11,"val":0},{"col":2,"row":1,"val":1},{"col":3,"row":2,"val":2}]

3.1、思考:

为了数据转化方便我偷懒存储了序列化的map字符串。应不应该考虑写入文件的数据量大小呢?我个人觉得这个问题还是从空间上考虑吧,比较程序执行速度是超快的。(这里就不展示纯文本格式的转化了,不是本文重点)

下面展示棋盘数据转化的几个阶段👇

chessMap     [[0 0 0 0 0 0 0 0 0 0 0] [0 0 1 0 0 0 0 0 0 0 0] [0 0 0 2 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0 0]]
sparseArr    [{11 11 0} {1 2 1} {2 3 2}]
sparseArrObj [map[col:11 row:11 val:0] map[col:2 row:1 val:1] map[col:3 row:2 val:2]]

4、完整代码展示

// 稀疏数组 - 保留棋盘
package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"os"
)

type ValNode struct {
	row int
	col int
	val int // int
}

func main() {
	// 1. 原始数组, 二维数组
	var chessMap [11][11]int
	chessMap[1][2] = 1 // 1 - 黑子
	chessMap[2][3] = 2 // 1 - 蓝子
	chessMap[5][5] = 2 // 1 - 蓝子

	// 2. 输出看看原始的数组
	for _, v := range chessMap {
		for _, v2 := range v {
			fmt.Printf("%d \t", v2)
		}
		fmt.Println()
	}

	// 3. 转成新数组 - 稀疏数组
	// 遍历数组,把值不为0的,创建一个 ValNode 结构体实例, 然后放入对应的切片

	// 定义切片
	var sparseArrObj []map[string]int

	// 标准的稀疏数组,含有记录二维数组的规模
	var obj map[string]int = make(map[string]int)
	obj["row"] = 11
	obj["col"] = 11
	obj["val"] = 0
	sparseArrObj = append(sparseArrObj, obj)
	fmt.Println("sparseArrObj", sparseArrObj)

	for row, v := range chessMap {
		for col, v2 := range v {
			if v2 != 0 {
				var obj map[string]int = make(map[string]int)
				obj["row"] = row
				obj["col"] = col
				obj["val"] = v2
				sparseArrObj = append(sparseArrObj, obj)
			}
		}
	}

	// 4. 输出稀疏数组
	fmt.Println("map切片:")
	for i, _map := range sparseArrObj {
		fmt.Printf("%d: %d\t%d\t%d\n", i, _map["row"], _map["col"], _map["val"])
	}

	// 存盘退出 续上盘
	// 5. 存盘 : 稀松数组保存到文件 chessmap.data
	fmt.Println("存盘数据, 'd:/chessmap.data'")
	filepath := "d:/chessmap.data"
	file, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Println("打开文件失败")
		return
	}
	defer func() {
		err = file.Close()
		if err != nil {
			fmt.Println("关闭文件错误")
		}
	}()
	// 写入数据, 带缓冲, writer.Flush()
	writer := bufio.NewWriter(file)

	// 5.1 序列化数据,存入文件,
	data, err := json.Marshal(sparseArrObj)
	if err != nil {
		fmt.Println("序列化失败", err)
		return
	}
	fmt.Println("sparseArrObj 序列化后: ", string(data))

	writer.WriteString(string(data))
	// 缓存落盘
	writer.Flush()

	// 6. 续上盘 :读取 chessmap.data 文件,遍历每一行, 恢复数组
	_file, err := os.OpenFile(filepath, os.O_RDWR|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("打开文件失败")
		return
	}

	// 6.1 读取内容
	fmt.Println("文件读取输出")
	var oldData string
	reader := bufio.NewReader(_file)
	for {
		// 读到一个换行就结束
		oldData, err = reader.ReadString('\n')
		// 输出内容
		fmt.Println("oldData=", oldData)
		if err == io.EOF {
			// 读到一行末尾
			break
		}
	}
	// 6.2 反序列化
	var _sparseArrObj []map[string]int
	err = json.Unmarshal([]byte(oldData), &_sparseArrObj)
	if err != nil {
		fmt.Println("反序列化失败", err)
		return
	}
	fmt.Println("反序列化结果: ", _sparseArrObj)

	// 7. 还原原始数组, 这里写死数组元素的个数, 应该使用切片,
	var newChessMap [11][11]int
	for i, _map := range _sparseArrObj {
		if i != 0 {
			newChessMap[_map["row"]][_map["col"]] = _map["val"]
		}
	}
	// 7.1 输出原始数组
	fmt.Println("======================")
	for _, v := range newChessMap {
		for _, v2 := range v {
			fmt.Printf("%d \t", v2)
		}
		fmt.Println()
	}
	fmt.Println("======================")

}

4.1、输出结果展示

0       0       0       0       0       0       0       0       0       0       0 
0       0       1       0       0       0       0       0       0       0       0 
0       0       0       2       0       0       0       0       0       0       0 
0       0       0       0       0       0       0       0       0       0       0 
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       2       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
sparseArrObj [map[col:11 row:11 val:0]]
map切片:
0: 11   11      0
1: 1    2       1
2: 2    3       2
3: 5    5       2
存盘数据, 'd:/chessmap.data'
sparseArrObj 序列化后:  [{"col":11,"row":11,"val":0},{"col":2,"row":1,"val":1},{"col":3,"row":2,"val":2},{"col":5,"row":5,"val":2}]
文件读取输出
oldData= [{"col":11,"row":11,"val":0},{"col":2,"row":1,"val":1},{"col":3,"row":2,"val":2},{"col":5,"row":5,"val":2}]
反序列化结果:  [map[col:11 row:11 val:0] map[col:2 row:1 val:1] map[col:3 row:2 val:2] map[col:5 row:5 val:2]]
======================
0       0       0       0       0       0       0       0       0       0       0
0       0       1       0       0       0       0       0       0       0       0
0       0       0       2       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       2       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
======================

5、小结

一个原则:以时间换空间,以空间换时间。在适当的时候选择合适的方案,这是编程语言解决性能问题的常用套路。


🎈🎈🎈

🌹 关注我,你会发现一个踏实努力的宝藏前端😊,让我们一起学习,共同成长吧。

🎉 喜欢的小伙伴记得点赞关注收藏哟,回看不迷路 😉

✨ 欢迎大家转发、评论交流

🎊 蟹蟹😊

本文正在参加技术专题18期-聊聊Go语言框架