使用VUE实现快速排序可视化
VUE 特点
VUE是以数据为驱动的,且内置了双向绑定 v-model
双向绑定的本质是为对象添加property,并且重写 get
,set
方法,当然也可以使用为对象添加代理 proxy
进行实现
手撸“双向绑定”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>双向绑定</title>
</head>
<body>
<input id="input" type="text" value="">
<span id="span"></span>
</body>
<script>
var dom1 = document.getElementById("input");
var dom2 = document.getElementById("span");
var data = {
'message':""
}
Object.defineProperty(data,'_message',{
get() {
return data.message;
},
set(value){
if (data.message != value)
data.message = value;
console.log(value);
}
})
dom1.addEventListener('input',function (e) {
data._message = e.target.value;
span.innerHTML = data.message;
})
</script>
</html>
排序可视化分析
排序可视化主要有两种实现方式
- queue 队列
- Generator 迭代器
队列即提前做好排序工作获取所有的列表状态 state
然后通过定时器将状态逐一抛出,实现可视化
迭代器即利用委托,利用 next
获取返回的动作,并通过定时器实现可视化
本文使用迭代器实现
变量的定义
list_str :'', // 与用户交互双向绑定
list : [], // 通过watch监听list_str实时修改list数组 用于排序
iter:null, // 迭代器
isStart:false, // 标志是否正在排序
isPause:false, // 标志是否暂停排序
sentinel:[{'name':'哨兵1','index':0},{'name':'哨兵2','index':0}], // 存储哨兵位置
queue : [], // 用于上一步下一步 暂未实现
info : '已停止', // 存放输出信息
重要函数的定义
function * f(left,right) //委托管理
start_sort() // 开始排序
stop_sort() // 结束排序
refuse_sort() // 恢复排序
pause_sort() // 暂停排序
定时器
定时器是最后执行的。
var sleep = 500;
var start= ()=>{ // 匿名函数
/*
main code
*/
setTimeout(start,sleep );
};
setTimeout(start,0);
具体实现
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>QuickSort Viewer</title>
<link rel="stylesheet" type="text/css" href="main.css" />
<link rel="stylesheet" type="text/css" href="layui/css/layui.css" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="jquery-1.11.3.min.js"></script>
<script src="layer/layer.js"></script>
<script src="layui/layui.js"></script>
<script src="main.js"></script>
</head>
<body>
<div id="app">
<div class="header">
<span style="color: #f0f0f0">QuickSort Viewer</span>
<div class= "flex-box">
<div class="layui-inline">
<div class="layui-input-inline">
<input type="text" autocomplete="off" placeholder="" class="layui-input" v-model="list_str">
</div>
</div>
<div class="layui-inline" style="margin-left: 10px">
<div class="layui-btn-group">
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" :class="{'layui-btn-disabled':isStart && !isPause}" @click="isStart?refuse_sort():start_sort()"><i class="layui-icon layui-icon-play"></i></button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" :class="{'layui-btn-disabled':!(!isPause && isStart)}" @click="pause_sort()"><i class="layui-icon layui-icon-pause
"></i></button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" :class="{'layui-btn-disabled':!isStart}" @click="stop_sort()"><i class="layui-icon layui-icon-delete
" ></i></button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" :class="{'layui-btn-disabled':isStart}" @click="get_random()"><i class="layui-icon layui-icon-set
"></i></button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="left" id="view">
<div class="canvas" v-if="isStart" :style="{ height:scale + 'px'}">
<div :class="{box:true}" v-for="(item,index) in sentinel"
:style="{ width:scale + 'px',height:scale + 'px',transform: 'translateX(' + item.index * 100 + '%)' }">
<div :class="{item:true,select:item.select,disable:item.disable}">{{item.name}}</div>
</div>
</div>
<div class="canvas" :style="{ height:scale + 'px'}">
<div :class="{box:true}" v-for="(item,index) in list"
:style="{width:scale + 'px',height:scale + 'px', transform: 'translateX(' + item['index'] * 100 + '%)' }">
<div :class="{item:true,select:item.select,disable:item.disable}" >{{item.value}}</div>
</div>
</div>
</div>
<div class="right">
<div class="info">
<p>当前步骤:</p>
<p>{{info}}</p>
</div>
<div class="author">
<div style="width:100px;height: 100px;border-radius:50px;margin-bottom:20px;background-image: url(https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1587314370721&di=f5ff40d6dc2ed7f563dee580495804bd&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fforum%2Fw%3D580%2Fsign%3D06f68d6457da81cb4ee683c56267d0a4%2F9a10ae246b600c332cdcbb581c4c510fd8f9a169.jpg);"></div>
<p> STDCHI </p>
</div>
<button type="button" class="layui-btn layui-btn-fluid">关于可视化的说明</button>
</div>
</div>
</div>
</body>
</html>
js
function _alert(msg,icon) {
layer.msg(msg, {icon: icon});
}
var sleep= 500;
var ARRAY = [3,100,2,1,4,5,9]; // global var
var INDEX = [0,1,2,3,4,5,6];
function swap(id1,id2) {
var t;
t = ARRAY[id1];
ARRAY[id1] = ARRAY[id2];
ARRAY[id2] = t;
t = INDEX[id1];
INDEX[id1] = INDEX[id2];
INDEX[id2] = t;
}
function * f(left,right) {
if (left >= right) {
if (left == right)yield {'method':'disable','data':INDEX[left]};
return;
}
var i,j,temp;
i = left;j = right;
temp = ARRAY[left];
yield {'method':'moves','data':[[1,left],[2,right]]};
yield {'method':'select','data':INDEX[left]};
while(i != j){
while(ARRAY[j] >= temp && i < j) {
j--;
yield {'method':'move','data':[2,j]};
}
while(ARRAY[i] <= temp && i < j) {
i++;
yield {'method':'move','data':[1,i]};
}
if(i < j)
{
swap(i,j);
yield {'method':'selects','data':[INDEX[i],INDEX[j]]};
yield {'method':'swap','data':[INDEX[i],INDEX[j]]};
yield {'method':'deselects','data':[INDEX[i],INDEX[j]]};
}
}
yield {'method':'deselect','data':INDEX[left]};
yield {'method':'disable','data':INDEX[left]};
swap(left,i);
yield {'method':'swap','data':[INDEX[left],INDEX[i]]};
// 递归委托
yield * f(left,i - 1);
yield * f(i + 1,right);
}
var App = new Vue({
el:'#app',
data(){
return{
list_str :'', // 与用户交互的字符串
list : [], // 排序使用的数组 [{index,value}]
iter:null, // 迭代器
isStart:false, // 是否开启
isPause:false, // 是否暂停
sentinel:[{'name':'哨兵1','index':0},{'name':'哨兵2','index':0}],
queue : [],
info : '已停止',
scale:0,
}
},
methods:{
reset_list(){
this.list_str = '3,100,2,1,4,5,9'
},
init_sort(){
// sort init
this.iter = f(0,this.list.length - 1);
ARRAY = [];
INDEX = [];
this.sentinel = [{'name':'哨兵1','index':0},{'name':'哨兵2','index':this.list.length - 1}];
for (let i = 0; i < this.list.length; i++) {
this.list[i].index = i;
this.list[i].select = false;
this.list[i].disable = false;
ARRAY.push(this.list[i].value);
INDEX.push(i);
}
},
start_sort(){
if(this.isStart && !this.isPause) return;
if (!this.isPause) {
this.init_sort();
}
this.isStart = true;
this.isPause = false;
var start = ()=>{
if(!this.isStart || this.isPause) return;
var i = this.iter.next();
if (!i.done){
console.log(i.value.method,i.value.data);
var a,b,t;
if(i.value.method == 'swap'){
a = i.value.data[0];
b = i.value.data[1];
this.set_info("交换(" + this.list[a].index.toString()+ " | " + + this.list[b].index.toString()+ ")" );
t = this.list[a].index;
this.list[a].index = this.list[b].index;
this.list[b].index = t;
}else if(i.value.method == 'move'){
a = i.value.data[0];
b = i.value.data[1];
this.sentinel[a - 1].index = b;
this.set_info("哨兵[" + a.toString()+ "] move --> " + b.toString());
}else if(i.value.method == 'moves'){
a = i.value.data;
for (let j = 0; j < a.length; j++) {
this.sentinel[a[j][0] - 1].index = a[j][1];
}
}else if(i.value.method == 'select'){
a = i.value.data;
this.list[a].select = true;
this.set_info("选择基准点(" + this.list[a].index.toString()+ ")" );
}else if(i.value.method == 'selects'){
a = i.value.data;
for (let j = 0; j < a.length; j++) {
this.list[a[j]].select = true;
}
}else if(i.value.method == 'deselect'){
a = i.value.data;
this.list[a].select = false;
}else if(i.value.method == 'deselects'){
a = i.value.data;
for (let j = 0; j < a.length; j++) {
this.list[a[j]].select = false;
}
}else if(i.value.method == 'disable'){
a = i.value.data;
this.list[a].disable = true;
}
}else if(i.done){
this.stop_sort();
}
setTimeout(start,sleep);
};
setTimeout(start,0);
},
stop_sort(){
if(!this.isStart) return;
this.isStart = false;
this.isPause = false;
this.set_info("已停止");
_alert("排序已结束",1);
},
refuse_sort(){
if(!this.isStart || !this.isPause) return;
this.start_sort();
},
pause_sort(){
if(!this.isStart || this.isPause) return;
this.isPause = true;
this.set_info("已暂停");
},
get_random(){
var num = Math.round(Math.random() * 2) + 10; // 10 - 12
var arr = [];
for (let i = 0; i < num; i++) {
arr.push(Math.round(Math.random() * 1000)); // 1000以内
}
this.list_str = arr.toString();
},
set_info(msg){
this.info = msg;
}
},
watch:{
// 变量监听
list_str: function (val,ordval) {
this.list = [];
let list = val.split(',').map(obj => parseInt(obj));// 映射为整数数组
for (let i = 0; i < list.length; i++) {
this.list.push({'index':i,'value':list[i],'select':false,'disable':false});
}
var w = document.getElementById("view").offsetWidth;
this.scale = parseInt(w / this.list.length);
console.log(this.list);
}
}
})
setTimeout(()=>{
App.reset_list();
App.get_random();
_alert("Hello!!!",6);
},0);
CSS
*{
padding: 0;
margin: 0;
transition: -webkit-transform 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition: transform 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition: transform 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275), -webkit-transform 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
#app{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: fixed;
}
.header{
height: 40px;
width: 100%;
box-shadow: 0 0 1px rgba(0,0,0,0.25);
transition: background-color 0.3s ease-in-out;
position: relative;
background-color: #393D49;
padding: 10px 60px;
z-index: 20;
font-size: 20px;
display: flex;
align-items: center; /*垂直居中对齐*/
}
.flex-box{
display: flex;
padding-left: 10px;
align-items: center; /*垂直居中对齐*/
}
.canvas {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
box-sizing:border-box;
}
.box{
padding: 10px;
float: left;
position: absolute;
text-align: center;
font-size: 20px;
box-sizing: border-box;
}
.item{
width: 100%;
height: 100%;
border: 1px solid #f3f3f3;
border-radius: 10px;
display: flex;
justify-content: center; /*水平居中对齐*/
align-items: center; /*垂直居中对齐*/
}
.container{
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.left{
display: flex;
flex-direction: column;
width: 65%;
height: 100%;
}
.right{
width: 30%;
height: 100%;
background-color: #f0f0f0;
box-shadow: 0 2px 2px rgba(10,16,20,.24),0 0 2px rgba(10,16,20,.12);
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
}
.info{
position: absolute;
width: 100%;
height: 100%;
font-size: 30px;
}
.author{
width: 100%;
height: 100%;
padding: 20px;
box-sizing: border-box;
font-size: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.select{
border: 2px solid coral;
/*margin-top: 30px;*/
}
.disable{
background-color: #f0f0f0;
}
.active{
/*background-color:blue;*/
}
后续的工作
后续我将封装可视化组件,使用VUECli-3脚手架开发,并且将上传到我的Github上