Skip to content

Commit

Permalink
Add ProperSequence building function. Improve user experience.
Browse files Browse the repository at this point in the history
Tree samples are randomly generated now.
  • Loading branch information
hwc0919 committed Mar 5, 2020
1 parent ada0d68 commit b2026d7
Show file tree
Hide file tree
Showing 12 changed files with 434 additions and 156 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ To run the program, you only need to download contents in folder 'dist'.

No compatibility testing. Functions are only guaranteed in Chrome.

若要运行程序, 只需要下载文件夹"dist"中的内容即可.
若要运行程序, 只需要下载文件夹"dist"中的内容即可(项目容量很小, 也可全部下载).

没有测试兼容性, 只能保证chorme浏览器下正常运行.

感谢
[@Wasted-waste](https://github.com/Wasted-waste),
[@B5DX](https://github.com/B5DX),
[@lijingwei233](https://github.com/lijingwei233),
[@baijiangying](https://github.com/baijiangying),
等同学对调试的帮助的建议

## Objective
I create this project for the purpose of helping those who are learning BBST in Data Structure course.
Expand Down
14 changes: 7 additions & 7 deletions dist/TreePlayground.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<div id="TreePlayground" @mousemove='onTPMouseMove($event)' @touchmove='onTPMouseMove($event)'>
<!-- Top Toolbar -->
<div class="top-toolbar">
<button class="btn btn-primary top-toolbar-item" type="button" @click="loadSampleTree">Sample</button>
<button class="btn btn-primary top-toolbar-item" type="button" @click="loadSampleTree(); update()">Sample</button>
<button class="btn btn-primary top-toolbar-item" type="button" @click="traversal(0)">先序遍历</button>
<button class="btn btn-primary top-toolbar-item" type="button" @click="traversal(1)">中序遍历</button>
<button class="btn btn-primary top-toolbar-item" type="button" @click="traversal(2)">后序遍历</button>
Expand All @@ -38,22 +38,22 @@ <h4>Scale: <label v-text="commonParams.treeScale + '%'"></h4>
</div>
<!-- Tree Visualization Part -->
<div class="tree" ref="tree" :style="adjustScale" style="transform-origin: top;"
@mousedown.self="onTreeMouseDown($event)" @mouseup.self="onTreeMouseLeave($event)"
@touchstart.self="onTreeMouseDown($event)" @touchend.self="onTreeMouseLeave($event)">
@mousedown.self="onTreeMouseDown" @mouseup.self="onTreeMouseLeave"
@touchstart.self="onTreeMouseDown" @touchend.self="onTreeMouseLeave">
<!-- Top Functional Node -->
<top-binnode id="trvl-sequence" :data="topSequence" @top-build="onTopBuild" @top-insert="onTopInsert"
@top-search="onTopSearch"></top-binnode>
@top-search="onTopSearch" @top-help="onTopHelp" @top-proper="onTopProper"></top-binnode>
<div class="left-message">{{ messages.left }}</div>
<div class="right-message">{{ messages.right }}</div>
<!-- Internal Tree Nodes -->
<binnode v-for="(node, ind) in structInfo.nodes" :node="node" :key="'node' + ind"
:class="{'active-node': node.active, 'visited-node': node.visited}"
@remove-below="onRemoveBelow($event)" @remove-one="onRemoveOne($event)"
@intr-update="onIntrUpdate($event)">
@remove-below="onRemoveBelow" @remove-one="onRemoveOne"
@intr-update="onIntrUpdate">
</binnode>
<!-- External Tree Nodes -->
<extr-binnode v-for="(node, ind) in structInfo.extrNodes" :node="node" :key="'extNode' + ind"
@extr-insert="onExtrInsert($event)">
@extr-insert="onExtrInsert">
</extr-binnode>
<!-- Internal Tree Edges -->
<div class="left-edge" v-for="e in structInfo.edges[0]"
Expand Down
227 changes: 164 additions & 63 deletions dist/bundle.js

Large diffs are not rendered by default.

72 changes: 44 additions & 28 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var vm = new Vue({
console.log("Load default tree.")
this.loadSampleTree();
}
this.reset();
},
reset() {
console.log("Reset");
Expand All @@ -62,9 +63,8 @@ var vm = new Vue({
loadFromHistory() { },
loadSampleTree() {
this.tree = this.curTreeClass.genSampleTree();
this.update();
},
alertAsync(message, time = 1000) {
alertAsync(message, time = 1500) {
this.messages.right = message;
let tag = ++this.alertTag;
setTimeout((e = tag) => {
Expand Down Expand Up @@ -111,48 +111,36 @@ var vm = new Vue({

// Events Handlers
onIntrUpdate(args) { // Internal node requests for value update
console.log("onIntrUpdate");
let node = args[0];
let updation = args[1];
let successMessage = `Change ${node.data} to ${updation}`;
if (this.curTreeType !== "BinTree") {
if (this.tree.search(updation)) {
alert("Already exists!");
return false;
} else if (BinNode.isLC(node) && updation > node.parent.data ||
BinNode.isRC(node) && updation < node.parent.data ||
node.lc && updation < node.lc.data ||
node.rc && updation > node.rc.data) {
alert("Must maintain order.");
this.alertAsync(`${updation} Exists!`);
return false;
}
if (!this.checkNodeOrder(node, updation)) return false;
}
node.data = updation;
this.update();
},
this.messages.left = successMessage;
}, // TODO: active newly updated node. Update before and after every action.
onExtrInsert(args) { // External node requests for value insertion
console.log("onExtrInsert");
let node = args[0];
let insertion = args[1];
let curTreeType = this.curTreeType;

if (curTreeType === "Splay") {
alert("Can't insert at external nodes in SplayTree.");
this.alertAsync("Can't insert at external nodes in SplayTree.", 3000);
return false;
}
if (curTreeType !== "BinTree") {
if (this.tree.search(insertion)) { // Decline duplicate
alert("Already exists!");
return false;
}
// pred and succ of parent
let pred, succ;
if (node.isLC === true && insertion > node.parent.data ||
node.isLC === true && (pred = node.parent.pred()) && insertion < pred.data ||
node.isLC === false && insertion < node.parent.data ||
node.isLC === false && (succ = node.parent.succ()) && insertion > succ.data) {
alert("Must maintain order.");
this.alertAsync(`${insertion} Exists!`);
return false;
}
// check new order
if (!this.checkNodeOrder(node, insertion)) return false;
}
var updateH;
if (curTreeType === "BinTree" || curTreeType === "BST")
Expand All @@ -169,15 +157,28 @@ var vm = new Vue({

if (curTreeType === "AVL") {
this.tree.search(insertion); // locate _hot
this.tree.solveInsertUnbalance();
this.tree.solveInsertUnbalance(); // TODO: change to async
}
this.update();
this.messages.left = `Insert ${insertion}`;
},
checkNodeOrder(node, newV) {
let pred, succ;
let isLC = node.isLC || BinNode.isLC(node);
if (isLC === true && newV > node.parent.data ||
isLC === true && (pred = node.parent.pred()) && newV < pred.data ||
isLC === false && newV < node.parent.data ||
isLC === false && (succ = node.parent.succ()) && newV > succ.data ||
node.lc && newV < node.lc.data || node.rc && newV > node.rc.data) {
this.alertAsync("Must maintain order.", 2500);
return false;
} return true;
},
// Remove whole subtree
onRemoveBelow(node) {
this.tree.removeBelow(node);
this.update();
this.alertAsync(`Remove Below ${node.data}`, 1000);
this.alertAsync(`Remove Below ${node.data}`);
},
// Remove one node
onRemoveOne(node) {
Expand All @@ -189,7 +190,7 @@ var vm = new Vue({
}
else if (0) { }
this.update();
this.alertAsync(`Remove ${node.data}`, 1000);
this.alertAsync(`Remove ${node.data}`);
},
// Proper Rebuild
onTopBuild(sequence) {
Expand All @@ -198,6 +199,10 @@ var vm = new Vue({
this.tree.buildFromBinSequence(sequence);
this.update();
this.messages.left = "真二叉树层次序列构建";

this.curTreeClass.checkValidity(this.tree, (res, message) => {
if (!res) this.alertAsync(message, 3000);
})
},
// Insert sequence
onTopInsert(sequence) {
Expand Down Expand Up @@ -229,6 +234,18 @@ var vm = new Vue({
else Math.random() < 0.5 ? this.alertAsync("Not Found") : this.alertAsync("404");
});
},
onTopHelp(message) {
this.alertAsync(message, 5000);
},
// Proper Binary Tree Sequence
onTopProper() {
let sequence = BinTree.properTraversal(this.tree.root());
for (let i = 0; i < sequence.length; i++) sequence[i] = sequence[i] ? sequence[i].data : null;
let last = sequence.length - 1;
while (sequence[last] === null) last--;
sequence.splice(last + 1);
this.topSequence = sequence;
},
searchAsync(node, num, callback) {
if (!this.trvlParams.lock || !node) {
this.trvlParams.lock = false;
Expand Down Expand Up @@ -293,7 +310,7 @@ var vm = new Vue({
},
strToArr(str) {
str = str.trim();
if (str === "") return false;
if (str === "") return null;
let arr = str.split(/,|,/);
for (let i = 0; i < arr.length; i++) {
arr[i] = this.assertNumber(arr[i]);
Expand All @@ -306,7 +323,6 @@ var vm = new Vue({
get() { return this.trees[this.curTreeType]; },
set(newTree) {
this.trees[this.curTreeType] = newTree;
this.update();
}
},
curTreeType: {
Expand Down
50 changes: 35 additions & 15 deletions src/components/components.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Vue from "../js/vue"
import { BinNode } from "../js/BinNode";


// Input Component with Self-ajusted-width
Vue.component('binnode-input', {
data: function () {
return {
Expand All @@ -24,12 +25,15 @@ Vue.component('binnode-input', {
},
watch: {
value() {
this.width = this.$refs.widthIndicator.offsetWidth;
process.nextTick(() => {
this.width = this.$refs.widthIndicator.offsetWidth;
})
}
}
})


// Internal BinNode
Vue.component('binnode', {
props: ['node'],
data() {
Expand Down Expand Up @@ -87,7 +91,7 @@ Vue.component('extr-binnode', {
props: ['node'],
template:
`<div class="binnode extr-binnode" :style="{'left': node.x + 'px', 'top': node.y + 'px'}" @click="divOnClick">
<binnode-input ref="input" v-show="showInput" v-model="insertion" @blur.native="showInput = false"
<binnode-input ref="input" v-show="showInput" v-model="insertion" @blur="showInput = false"
@keyup.enter.native="emitExtrInsert">
</binnode-input>
</div>
Expand All @@ -102,18 +106,24 @@ Vue.component('extr-binnode', {
divOnClick() {
if (this.showInput === true) return false;
this.showInput = true;
setTimeout(() => {
process.nextTick(() => {
this.$refs.input.forceFocus();
}, 1);
})
}
}
});

{/* */ }

// Top Funtion Node
Vue.component('top-binnode', {
data() {
return { sequence: this.data.toString(), showInput: false };
return {
sequence: this.data.toString(), showInput: false, helpInd: -1,
helpMessages: ["B: 真二叉树构建, 逗号分隔. 例如 1,,2,,3 对应 {1: [null, 2: [null, 3]]}",
"I: (回车)输入序列依次插入, 逗号分隔. 上限为666..666(具体几个不告诉你)",
"S: 输入单个数值进行搜索",
"P: 生成真二叉树层次遍历序列(复制下来就可以随时重建啦)"]
};
},
props: ['data'],
template:
Expand All @@ -123,32 +133,34 @@ Vue.component('top-binnode', {
@click.stop="emitTopBuild"><i>B</i></label>
<label v-show="showTopInsert" class="top-insert-btn node-upper-btn" title="按次序插入" @click.stop="emitTopInsert"><i>I</i></label>
<label v-show="showTopSearch" class="top-search-btn node-upper-btn" title="查找单个数值" @click.stop="emitTopSearch"><i>S</i></label>
<label class="top-help-btn node-left-btn" title="help" @click.stop="emitTopHelp"><i>?</i></label>
<label class="top-proper-btn node-upper-btn" title="生成真二叉序列" @click.stop="$emit('top-proper')"><i>P</i></label>
<binnode-input ref="input" v-show="showInput" v-model="sequence" @blur="showInput=false" @keyup.enter.native="emitTopInsert">
</binnode-input>
</div>`,
methods: {
divOnClick() {
if (this.showInput === true) return false;
this.showInput = true;
setTimeout(() => {
process.nextTick(() => {
this.$refs.input.forceFocus();
}, 1);
})
},
emitTopBuild() {
if (this.$parent.tree && this.$parent.tree.root())
if (!confirm("Overwrite current tree?")) return false;

if (this.sequence === "") return false;
let sequence = this.$parent.strToArr(this.sequence);
this.sequence = sequence.toString();

if (sequence[0] === null) {
if (!sequence || sequence[0] === null) {
alert("Empty Root!");
return false;
}
if (this.$parent.tree && this.$parent.tree.root())
if (!confirm("Overwrite current tree?")) return false;
this.sequence = sequence.toString();
this.$emit('top-build', sequence);
},
emitTopInsert() {
let sequence = this.$parent.strToArr(this.sequence);
if (!sequence || sequence.length === 0) return false;
this.sequence = sequence.toString();
this.$emit('top-insert', sequence);
},
Expand All @@ -157,6 +169,14 @@ Vue.component('top-binnode', {
if (num === null) return false;
this.sequence = num.toString();
this.$emit('top-search', num);
},
emitTopHelp() {
this.helpInd = ++this.helpInd % this.helpMessages.length;
let enableHelp = [this.showTopBuild, this.showTopInsert, this.showTopSearch, true]
let failSafe = 0;
while (!enableHelp[this.helpInd] && failSafe++ < 5)
this.helpInd = ++this.helpInd % this.helpMessages.length;
if (enableHelp[this.helpInd]) this.$emit('top-help', this.helpMessages[this.helpInd]);
}
},
computed: {
Expand Down

0 comments on commit b2026d7

Please sign in to comment.