node ts完成课程设计
一、背景
就于前几日,我们数据结构老师发布了课程设计:
问题描述:建立身份证信息管理系统,能够进行身份证信息的录入、查找,保存,要求考虑查找效率,用二叉排序树存储信息。 具体功能有:
(1)能够进行身份证号码及相关信息的录入,相关信息包括姓名、地址和手机号;
(2)能够快速进行身份证号码的查询,并输出相关信息;
(3)可以修改身份证号码对应的其他信息,如姓名、地址;
(4)可以完成身份证信息的删除。
(5)信息保存到数据文件中。
但我一深思,我c语言差不多一个学期没用了,这不得凉凉。
当我回到寝室开始上号
看到熟悉的ts语法,我突发奇想,这能用c来写,为啥就不能用ts来写。
二、途中曲折,一言难尽
代码语言:javascript复制//# BST.ts文件
import { IDData, BNode } from "./dataType";
class BST {
public root: BNode = null;
public BNode = class implements BNode {
data: IDData;
left: BNode = null;
right: BNode = null;
constructor(data: IDData) {
this.data = data;
}
}
//向树中插入节点
insert(data: IDData): void {
const newNode = new this.BNode(data);
if (this.root == null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode)
}
}
//寻找插入节点
private insertNode(root: BNode, node: BNode) {
//向左查找
if (Number(node.data.id) < Number(root.data.id)) {
//如果左子树空直接插入
if (root.left == null) {
root.left = node
} else {
//如果不空继续递归调用
this.insertNode(root.left, node)
}
} else {
if (root.right == null) {
root.right = node
} else {
this.insertNode(root.right, node)
}
}
}
//遍历数据(中序遍历)
inorderTraversal(): IDData[] {
const result: IDData[] = [];
this.preorderTraversalNode(this.root, result);
return result;
}
private preorderTraversalNode(node: BNode, result: IDData[]) {
if (node == null) return result;
this.preorderTraversalNode(node.left, result)
result.push(node.data)
this.preorderTraversalNode(node.right, result)
}
//查找
search(id: IDData['id']): IDData {
let node: BNode = this.root;
while (node) {
if (Number(id) < Number(node.data.id)) {
node = node.left
} else if (Number(id) > Number(node.data.id)) {
node = node.right
} else {
return node.data
}
}
}
//删除
delete(id: IDData['id']): IDData {
let currentNode: BNode = this.root;
let parentNode: BNode = null;
let isLeftChild: boolean = true;
//循环查找要删除的节点 currentNode,已及它的parrentNode,isLeftChild
while (currentNode.data.id != id) {
parentNode = currentNode;
if (Number(id) < Number(currentNode.data.id)) {
isLeftChild = true;
currentNode = currentNode.left;
} else {
isLeftChild = false;
currentNode = currentNode.right;
}
//找到最后没找到返回null
if (currentNode == null) {
return null;
}
}
//1.删除的是叶子节点
if (currentNode.left == null && currentNode.right == null) {
if (currentNode == this.root) {
this.root == null;
} else if (isLeftChild) {
parentNode.left = null;
} else {
parentNode.right = null;
}
//2.删除只有一个子节点的节点
} else if (currentNode.right == null) {//currentNode只存在左节点
//判断是否是根节点
if (currentNode == this.root) {
this.root = currentNode.left;
//判断currentNode是不是父节点的左子叶
} else if (isLeftChild) {
//如果是就让父节点的左子叶重接仅剩是currentNode的子叶
parentNode.left = currentNode.left
} else {
parentNode.right = currentNode.left
}
} else if (currentNode.left == null) {//currentNode只存在由节点
//判断是否是根节点
if (currentNode == this.root) {
this.root = currentNode.right
//判断currentNode是不是父节点的左子叶
} else if (isLeftChild) {
//如果是就让父节点的左子叶重接仅剩是currentNode的子叶
parentNode.left = currentNode.right
} else {
parentNode.right = currentNode.right
}
//3.删除的是两个子节点的情况(后继)
}else{
//1.找到后续节点
let successor=this.getSuccessor(currentNode);
//2.判断是否为根节点
if(currentNode==this.root){
this.root=successor;
}else if(isLeftChild){
parentNode.left=successor;
}else{
parentNode.right=successor;
}
//3.将后续的左节点改为被删除的左节点
successor.left=currentNode.left;
}
return currentNode.data;
}
private getSuccessor(delNode:BNode):BNode{
//保存要找到的后续
let successor:BNode=delNode;
let current:BNode=delNode.right;
let successorParent:BNode=delNode;
while(current!=null){
successorParent=successor;
successor=current;
current=current.left;
}
//判断寻找的后续节点是否直接就是要删除的节点的right
if(successor!=delNode.right){
successorParent.left=successor.right;
successor.right=delNode.right
}
return successor
}
}
export default BST
复制代码
声明文件
代码语言:javascript复制//# dataType.d.ts 文件
export declare enum Sex{
male="男",
female="女"
}
export declare interface IDData{
id:string,
name:string,
telephone:string,
address:string,
sex:Sex,
age:number
}
export declare interface BNode{
data:IDData,
left:BNode,
right:BNode
}
复制代码
上面是完整的二叉排序树代码,由于刚开始接触数据结构对算法等都还不是很熟悉,
这里参考了
JavaScript数据结构与算法
核心二叉树写好了,随之而来的就是以下几个问题:
1.在哪里运行?
2.如何接收命令行参数?
3.存储在什么格式的数据文件里?
4.怎样存储到数据文件里?
5.怎么提高用户体验?
二、发现问题并解决
1.在哪里运行?
毋庸置疑在node环境中运行,刚好前段时间也自学了一点进程,线程,net等模块。
我单纯就为了实践一下,把这次课程设计当作一份试卷检验一下以前学的知识。
2.如何接收命令行参数?
node官网教程里演示了readLine模块,这是一种不错的方法,我用的inquirer。
3.存储在什么格式的数据文件里?
当时室友在和我回寝室的路上,我室友学的java,我问他,你准备用什么格式的文件保存数据,他说:“txt”。
但我转念一想,txt文本好像并无格式可言,要是能放Excel表格里就好了。
当我回去打开vs code看到了包描述文件package.json时,我觉得可以用json数据保存,json保存的数据里都是键值对,对象和数组,这样我就可以把每一条身份信息放一个对象再存数组里。
4.怎样存储到数据文件里?
这里我用的fs模块一次性读入,写入。
5.怎么提高用户体验?
写个服务器结合vue整个网页?我转念一想,还有20天期末考试了,写完这个还有时间复习吗?
然后还是选择了控制台打印,当我实践过程中发现打印json字符串数据量一多就没法看了,能不能打印表格呢?经过一番搜索,找到了word-table这个包。并决定用chalk尝试过自己做个cli的同学应该都知道,还有一个commander,但我这里并不打算用这个。
以下直接上代码
代码语言:javascript复制//# src/CommandeLine/index.ts
import inquirer from "inquirer";
import chalk from "chalk";
enum Sex{
male="男",
female="女"
}
export async function init(){
return await inquirer.prompt([
{
type:'list',
name:'serve',
message:chalk.yellow('请选择服务:'),
choices:['录入身份信息','查询身份信息','查看所有数据','删除指定信息','修改指定信息','保存','退出']
}
])
}
export async function input(){
return await inquirer.prompt([
{
type:'input',
name:'name',
message:'请输入名字',
default:"张三"
},
{
type:'input',
name:'age',
message:'请输入年龄',
default:'18'
},
{
type:'confirm',
name:'sex',
message:'你的性别是男?',
default:'男'
},
{
type:'input',
name:'id',
message:chalk.greenBright('请输入身份证号'),
validate(value){
return !value.length ? new Error(chalk.red('身份证号不能为空')) : true
}
},
{
type:'input',
name:'address',
message:'请输入地址',
default:'未知'
},
{
type:'input',
name:'telephone',
message:'请输入电话号码'
}
])
}
export async function confirm(){
return await inquirer.prompt([{
type:"confirm",
name:"continueOperation",
message:chalk.blue('是否继续?'),
default:true
}])
}
export async function search(){
return await inquirer.prompt([
{
type:"input",
name:'id',
message:chalk.greenBright('请输入查询身份证号:'),
validate(value){
return !value.length ? new Error(chalk.red('身份证号不能为空')) : true
}
}
])
}
export async function modify(){
return await inquirer.prompt([
{
type:'input',
name:'name',
message:'请输入名字',
default:"张三"
},
{
type:'input',
name:'age',
message:'请输入年龄',
default:'18'
},
{
type:'confirm',
name:'sex',
message:'你的性别是男?',
},
{
type:'input',
name:'address',
message:'请输入地址',
default:'未知'
},
{
type:'input',
name:'telephone',
message:'请输入电话号码'
}
])
}
export async function signout(){
return await inquirer.prompt([
{
type:'confirm',
message:chalk.green('是否退出'),
name:'logout'
}
])
}
复制代码
在写的过程中进行模块化时,刚开始用的直接导出,没使用async,await,导致命令行提示问句时与预想不符合,后面尝试了一下回调的方法,但写起来容易造成回调地狱,由于inquirer直接支持promise所以我就写的这种。
简单说明一下:
在main.ts文件中我对operation.ts 和index.ts创建了子进程,operation.ts主要进行对二叉树的操作,index.ts主要是进行数据表格打印。另外我在operation.ts开启了另一个子进程readWrite.ts,
这也是第一次尝试,在子进程中再开一个子进程。readWrite.ts进程主要是对data.json文件读写。
以下是代码:
代码语言:javascript复制// # src/child_process/operation.ts
import BST from '../BST'
import net from 'net'
import ChildProcess from 'child_process'
import path from 'path'
//创建二叉树
const bst = new BST()
//必须用绝对路径(在子进程中当前工作文件变了所有相对路径找不到文件)
const child=ChildProcess.fork(path.join(__dirname,'readWrite'))
//建立tcp通信(客户端)
const client = net.createConnection({
port: 8000,
host: 'localhost'
})
client.on('connect', () => {
//建立连接后在操作数据
child.on('message',(data:any)=>{
//将json字符串解析成数组
data=JSON.parse(data)
//对数组遍历把每一项插入树中
data.forEach(i => {
bst.insert(i)
});
})
process.on('message', (msg: any) => {
if (msg.type == '查询') {
//console.log(msg.data)
const res1 = bst.search(msg.data)
let res = [];
res.push(res1)
client.write(JSON.stringify(res))
} else if (msg.type == '查询所有') {
//保存中序遍历数据
const res = bst.inorderTraversal()
//向子进程2发送数据打印表格
client.write(JSON.stringify(res))
} else if (msg.type == '录入') {
//向二叉树执行插入
msg.data.forEach(i => {
bst.insert(i)
});
} else if (msg.type == '删除') {
bst.delete(msg.data)
}else if (msg.type == '修改') {
//先删除
bst.delete(msg.data)
//再重新写入
//插入id
msg.IdData.id=msg.data
bst.insert(msg.IdData)
}else if (msg.type == '保存') {
//向子进程发送写入文件信号
let res:any=bst.inorderTraversal()
res=JSON.stringify(res,null,'t')
child.send(res)
}
})
})
client.on('data', (data) => {
console.log(data.toString())
})
复制代码
代码语言:javascript复制// # src/child_process/readWrite.ts
import fs from 'fs'
import util from 'util'
import path from 'path'
const read=util.promisify(fs.readFile);
const write=util.promisify(fs.writeFile);
async function readFile(path:string){
return await read(path);
}
async function writeFile(path:string,data:any){
return await write(path,data);
}
let main=async()=>{
let res=await readFile(path.join(__dirname,'../data.json'))
process.send(res.toString())
process.on('message',async(msg:string)=>{
await write(path.join(__dirname,'../data.json'),msg)
})
}
main()
复制代码
代码语言:javascript复制// # src/child_process/index.ts
import WordTable from 'word-table';
import chalk from 'chalk';
import net from 'net'
let header = [
"id",
"name",
"age",
"sex",
"telephone",
"address"
]
let body = [];
//建立tcp通信(服务端)
const server = net.createServer()
//监听8000端口
server.listen(8000)
server.on('connection', socket => {
//接收数据打印表格
socket.on('data', (data) => {
let msg = JSON.parse(data.toString())
msg.forEach(e => {
let arr = [];
arr.push(e.id)
arr.push(e.name)
arr.push(e.age)
arr.push(e.sex)
arr.push(e.telephone)
arr.push(e.address)
body.push(arr)
});
const wt = new WordTable(header, body)
//console.log(chalk.green('插入数据成功:'))
console.log(chalk.bgGray.yellow(wt.string()))
//重置body为下次打印准备
body=[]
})
})
复制代码
运行效果:
效果可能一般,但我觉得值,至少是自己肝了两天写好的。
三、总结
就这样我花了两天时间完成了我的课程设计,期间发现问题并解决问题,这是一个痛苦并快乐的事,我也发现了自己的一些问题:
一、typescript写的还不够好,在使用node自带模块时用成了anyscript
二、另外数据结构有待提升
三、在node执行以及node底层还有更多的地方等着去学习
在今年寒假,笔者会努力提升自己,再未来笔者也希望为社区做点贡献。
如果有一天,当你的努力配得上你的梦想,那么,你的梦想也绝对不会辜负你的努力。让自己尽可能变得优秀,当你为一件事情拼命努力的时候,全世界都会帮你!