Compare commits

..

105 Commits

Author SHA1 Message Date
JamesFlare1212
ab5cc82410 solve hw-10 2025-04-29 14:42:52 -04:00
Jidong Xiao
73be805725 update input data, no duplicates now 2025-04-29 14:42:52 -04:00
Jidong Xiao
78ead22162 push to the left side 2025-04-29 14:42:52 -04:00
Jidong Xiao
ab968cd2cc push to the left side 2025-04-29 14:42:52 -04:00
Jidong Xiao
7ceb05ce98 move tree to the right 2025-04-29 14:42:52 -04:00
Jidong Xiao
4903366103 testing width 2025-04-29 14:42:52 -04:00
Jidong Xiao
69ecea3dd9 reducing the box width 2025-04-29 14:42:52 -04:00
Jidong Xiao
8edd8a5221 renaming file 2025-04-29 14:42:52 -04:00
Jidong Xiao
1ffe82c107 reducing the width 2025-04-29 14:42:52 -04:00
Jidong Xiao
693b853865 updating min heap animation 2025-04-29 14:42:52 -04:00
Jidong Xiao
09273e9338 updating max heap animation 2025-04-29 14:42:52 -04:00
Jidong Xiao
506ab00200 updating hash table animation 2025-04-29 14:42:52 -04:00
Jidong Xiao
cc21696e46 adding hash table animations 2025-04-29 14:42:52 -04:00
Jidong Xiao
fea90f2b4d updating post order animation 2025-04-29 14:42:52 -04:00
Jidong Xiao
50bbf8c2a7 adding the map animation 2025-04-29 14:42:52 -04:00
Jidong Xiao
c3588e8541 renaming and adding author info 2025-04-29 14:42:52 -04:00
Jidong Xiao
643c7f1038 update animation 2025-04-29 14:42:52 -04:00
Jidong Xiao
083ae76743 updating morri post order 2025-04-29 14:42:52 -04:00
Jidong Xiao
efc2fbaaa3 adding the animation 2025-04-29 14:42:52 -04:00
Jidong Xiao
2d3840b312 adding pq animations 2025-04-29 14:42:52 -04:00
Jidong Xiao
6a17be892f fixing the file name 2025-04-29 14:42:52 -04:00
Jidong Xiao
d6ef9dff46 updating the animation path 2025-04-29 14:42:52 -04:00
Jidong Xiao
3fd45d0b8c adding iterative animation again 2025-04-29 14:42:52 -04:00
Jidong Xiao
421ae870a4 highlighting the sentence 2025-04-29 14:42:52 -04:00
Jidong Xiao
1383572b2f just rewrite, not re-rewrite 2025-04-29 14:42:52 -04:00
Jidong Xiao
2fa18a094f fixing the comments 2025-04-29 14:42:52 -04:00
Jidong Xiao
83e54834d8 min heap, still need to reverse 2025-04-29 14:42:52 -04:00
Jidong Xiao
28243a596d updating morris 2025-04-29 14:42:52 -04:00
Jidong Xiao
3c88acac9f remove useless characters 2025-04-29 14:42:52 -04:00
Jidong Xiao
97fd05d08c updating readme 2025-04-29 14:42:52 -04:00
Jidong Xiao
f187301c4a 2 pieces of paper this time 2025-04-29 14:42:52 -04:00
Jidong Xiao
4060e32a9a 28, not 27 2025-04-29 14:42:52 -04:00
Jidong Xiao
f320060f7b adding final info 2025-04-29 14:42:52 -04:00
Jidong Xiao
d0385b27f1 adding iterative traversal animations 2025-04-29 14:42:52 -04:00
Jidong Xiao
7b9d3983ed updating the animation 2025-04-29 14:42:52 -04:00
Jidong Xiao
c24a7592ab updating rubric, must be used in a meaningful way 2025-04-29 14:42:52 -04:00
Jidong Xiao
d4cd201b05 including shared ptr implementation 2025-04-29 14:42:52 -04:00
Jidong Xiao
415b60e129 adding shared ptr implementation 2025-04-29 14:42:52 -04:00
Jidong Xiao
64aa4ba11c short description of auto_ptr 2025-04-29 14:42:52 -04:00
Jidong Xiao
feeb84eae7 adding unique ptr example 2025-04-29 14:42:52 -04:00
Jidong Xiao
0fc129f674 adding unique ptr example 2025-04-29 14:42:52 -04:00
Jidong Xiao
284bd60029 adding the unique function line 2025-04-29 14:42:52 -04:00
Jidong Xiao
45124db960 adding the print age line 2025-04-29 14:42:52 -04:00
Jidong Xiao
2e224420e9 adding the print age line 2025-04-29 14:42:52 -04:00
Jidong Xiao
b8fde0d18e remove assigning a new object 2025-04-29 14:42:52 -04:00
Jidong Xiao
2f84e7ed34 adding member functions 2025-04-29 14:42:52 -04:00
Jidong Xiao
329699721f adding shared pointer example 2025-04-29 14:42:52 -04:00
Jidong Xiao
30fa11776d fixing angle brackets 2025-04-29 14:42:52 -04:00
Jidong Xiao
418411d62d adding smart pointer examples 2025-04-29 14:42:52 -04:00
Jidong Xiao
fd6ef6545e highlight the sentence 2025-04-29 14:42:52 -04:00
JamesFlare1212
a109046498 solve hw-9 2025-04-15 22:10:48 -04:00
Jidong Xiao
9ec5d3d32c fixing the path 2025-04-15 22:10:48 -04:00
Jidong Xiao
727b4fccd0 updated with pointers 2025-04-15 22:10:48 -04:00
Jidong Xiao
6670134276 not 8, but 6 2025-04-15 22:10:48 -04:00
Jidong Xiao
7eab384626 renaming 2025-04-15 22:10:48 -04:00
Jidong Xiao
728024087b renaming 2025-04-15 22:10:48 -04:00
Jidong Xiao
81a4824b0a renaming 2025-04-15 22:10:48 -04:00
Jidong Xiao
999bf68f77 renaming 2025-04-15 22:10:48 -04:00
Jidong Xiao
06c33e577b revising notes 2025-04-15 22:10:48 -04:00
Jidong Xiao
34d5900d2c adding polymorphism notes 2025-04-15 22:10:48 -04:00
Jidong Xiao
f858d57e31 format the numbers 2025-04-15 22:10:48 -04:00
Jidong Xiao
8102db4dac adding info about memory size for virtual functions 2025-04-15 22:10:48 -04:00
Jidong Xiao
63ddc1cc22 more about poly 2025-04-15 22:10:48 -04:00
Jidong Xiao
12e95dedfc re numbering 2025-04-15 22:10:48 -04:00
Jidong Xiao
114bc00e8b re-formatting the diamond 2025-04-15 22:10:48 -04:00
Jidong Xiao
33cf2cbeaa add pre 2025-04-15 22:10:48 -04:00
Jidong Xiao
e2c9cf9566 adding the diamond problem 2025-04-15 22:10:48 -04:00
Jidong Xiao
3514476080 show the importance of member initializer lists 2025-04-15 22:10:48 -04:00
Jidong Xiao
58b8a67d55 removing outdated content 2025-04-15 22:10:48 -04:00
Jidong Xiao
456cdb961a renaming 2025-04-15 22:10:48 -04:00
Jidong Xiao
628104fa39 updating the ordering rules for spring 26 semester 2025-04-15 22:10:48 -04:00
Jidong Xiao
877576afc5 include the pre order animation 2025-04-15 22:10:48 -04:00
Jidong Xiao
03021491c5 adding pre order animation 2025-04-15 22:10:48 -04:00
Jidong Xiao
5f34a80e42 fixing konvas path 2025-04-15 22:10:48 -04:00
Jidong Xiao
73b0ece069 check current first 2025-04-15 22:10:48 -04:00
Jidong Xiao
53549accd0 fixing the file paths 2025-04-15 22:10:48 -04:00
Samson Kempiak
7682f722f1 edit hw readme to add better line breaks 2025-04-15 22:10:48 -04:00
Jidong Xiao
3cf7288c34 adding post order animation 2025-04-15 22:10:48 -04:00
Jidong Xiao
302a2f176d adding morris post order 2025-04-15 22:10:48 -04:00
Jidong Xiao
bb38a699a0 include 2025-04-15 22:10:48 -04:00
Jidong Xiao
bfc95ce254 adding name hiding code 2025-04-15 22:10:48 -04:00
Jidong Xiao
89609d0937 adding name hiding example 2025-04-15 22:10:48 -04:00
Jidong Xiao
107a578154 max heap, not min heap 2025-04-15 22:10:48 -04:00
Jidong Xiao
1c454d2ecd not the push, but the sort 2025-04-15 22:10:48 -04:00
Jidong Xiao
425b39cf7f adding heap sort animation 2025-04-15 22:10:48 -04:00
Jidong Xiao
8e49abd24a adding heap sort animation 2025-04-15 22:10:48 -04:00
Jidong Xiao
d8190eeea8 update test 3 2025-04-15 22:10:48 -04:00
Jidong Xiao
235168710e about multi level inheritance 2025-04-15 22:10:48 -04:00
Jidong Xiao
28a8f7d285 adding student test 5 2025-04-15 22:10:48 -04:00
Jidong Xiao
110f6cc319 adding exercise 2 2025-04-15 22:10:48 -04:00
Jidong Xiao
7b9c74b098 adding exercise 1 2025-04-15 22:10:48 -04:00
Jidong Xiao
34f972e3f4 destructor 2025-04-15 22:10:48 -04:00
Jidong Xiao
7f7b936341 adding student program 2025-04-15 22:10:48 -04:00
Jidong Xiao
21fa2c03de renaming 2025-04-15 22:10:48 -04:00
Jidong Xiao
9096194d90 adding inheritance notes 2025-04-15 22:10:48 -04:00
Jidong Xiao
a46c3caa59 clarification 2025-04-15 22:10:48 -04:00
Jidong Xiao
1311832955 clarify the rules 2025-04-15 22:10:48 -04:00
Jidong Xiao
0f6391d57a renaming 2025-04-15 22:10:48 -04:00
Jidong Xiao
ae3b122fb1 adding hw10 2025-04-15 22:10:48 -04:00
bb359c9fe7 solve lab 9 2025-04-09 12:19:36 -04:00
Jidong Xiao
e41e5e4a0c list others as variations 2025-04-09 12:19:36 -04:00
Jidong Xiao
6bdc38ef77 switch to min heap now 2025-04-09 12:19:36 -04:00
Jidong Xiao
53824b1147 adding lab 2025-04-09 12:19:36 -04:00
Jidong Xiao
1ade7ffdde adding comments to heapify code 2025-04-09 12:19:36 -04:00
Jidong Xiao
47cbeb649f adding layer draw 2025-04-09 12:19:36 -04:00
165 changed files with 158460 additions and 68863 deletions

34
.vscode/launch.json vendored
View File

@@ -135,6 +135,40 @@
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"preLaunchTask": "C/C++: g++ build active file"
},
{
"name": "nytrends",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [
"inputs/input_large9.json",
"output.txt",
"hashtag"
],
"cwd": "${fileDirname}",
"environment": [],
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"preLaunchTask": "C/C++: g++ build active file"
},
{
"name": "nynotifications",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [
"posts.json",
"users.json",
"events_large.txt",
"output.txt",
"carrieunderwood"
],
"cwd": "${fileDirname}",
"environment": [],
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"preLaunchTask": "C/C++: g++ build active file"
}
]
}

View File

@@ -78,6 +78,11 @@
"unordered_set": "cpp",
"regex": "cpp",
"cinttypes": "cpp",
"__node_handle": "cpp"
"__node_handle": "cpp",
"shared_mutex": "cpp",
"cfenv": "cpp",
"locale": "cpp",
"filesystem": "cpp",
"__split_buffer": "cpp"
}
}

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Separate Chaining Based Hash Table</title>
<script src="../konva.js"></script>
</head>
<body>
<h1>Separate Chaining Based Hash Table</h1>
<p>This is the animation of Separate Chaining Based Hash Table. Click the "next step" button to run the animation.</p>
<button onclick="nextstep()">next step</button>
<div id="container"></div>
<script src="main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,734 @@
// Author: Xi Chen
// first we need to create a stage
var stage = new Konva.Stage({
container: 'container', // id of container <div>
width: 2000,
height: 1000
});
// then create layer
var layer = new Konva.Layer();
function makeMemory(xstart, ystart, bufferSize, w, h)
{
for (let i = 0; i < bufferSize; ++i)
{
let tr = new Konva.Rect
({
x: xstart,
y: ystart+i*h,
id:"brec"+i,
stroke: '#343434',
strokeWidth: 5,
fill: '#ddd',
width: w,
height: h,
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
});
layer.add(tr);
// Add index number to the left of each box
let indexText = new Konva.Text
({
x: xstart - 30, // Position to the left of the box
y: ystart + i*h + h/2 - 10, // Center vertically
text: i.toString(),
fontSize: 18,
fontFamily: 'Calibri',
fill: '#000000',
align: 'center',
width: 30,
});
layer.add(indexText);
} // end of for loop
}
makeMemory(1550,60,10,50,50);
var memoryText = new Konva.Text
({
x: 1525,
y: 25,
id:"memory",
text: "Memory",
fontSize: 28,
fontFamily: 'Calibri',
fill: '#000000',
});
var mainLabel = new Konva.Text
({
x: 400,
y: 0,
id:"main",
text: "Main",
fontSize: 28,
fontFamily: 'Calibri',
fill: '#000000',
});
// box for code
layer.add(memoryText);
layer.add(mainLabel);
// add the layer to the stage
stage.add(layer);
// draw the image
layer.draw();
const steps =
[
// Initialize phonebook
{mainHighlight: [1], funcHighlight: [], action: null, console: "Initializing phonebook array with size 10"},
// Add dan
{mainHighlight: [2], funcHighlight: [], action: null, console: "Adding entry: dan (5182764321)"},
{mainHighlight: [2], funcHighlight: [13], action: null, console: "Entering add function"},
{mainHighlight: [2], funcHighlight: [14], action: null, console: "Calculating hash index..."},
{mainHighlight: [2], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [2], funcHighlight: [11], action: null, console: "5182764321 % 10 = 1"},
{mainHighlight: [2], funcHighlight: [14], action: null, console: "Hash index: 1"},
{mainHighlight: [2], funcHighlight: [15], action: null, console: "Creating new node"},
{mainHighlight: [2], funcHighlight: [16], action: null, console: "Setting name: dan"},
{mainHighlight: [2], funcHighlight: [17], action: null, console: "Setting number: 5182764321"},
{mainHighlight: [2], funcHighlight: [18], action: {type: 'highlight_memory', index: 1, color: '#00FF00'}, console: "Setting next pointer"},
{mainHighlight: [2], funcHighlight: [19], action: {type: 'add', index: 1, name: 'dan', number: '5182764321', color: '#0000FF'}, console: "Adding node to index 1"},
{mainHighlight: [2], funcHighlight: [20], action: {type: 'highlight_memory', index: 1, color: '#343434'}, console: "Returning from add function"},
{mainHighlight: [2], funcHighlight: [20], action: {type: 'highlight_person', index: 1, name: 'dan', number: '5182764321', color: '#222'}, console: "Returning from add function"},
// Add fred
{mainHighlight: [3], funcHighlight: [], action: null, console: "Adding entry: fred (6173551212)"},
{mainHighlight: [3], funcHighlight: [13], action: null, console: "Entering add function"},
{mainHighlight: [3], funcHighlight: [14], action: null, console: "Calculating hash index..."},
{mainHighlight: [3], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [3], funcHighlight: [11], action: null, console: "6173551212 % 10 = 2"},
{mainHighlight: [3], funcHighlight: [14], action: null, console: "Hash index: 2"},
{mainHighlight: [3], funcHighlight: [15], action: null, console: "Creating new node"},
{mainHighlight: [3], funcHighlight: [16], action: null, console: "Setting name: fred"},
{mainHighlight: [3], funcHighlight: [17], action: null, console: "Setting number: 6173551212"},
{mainHighlight: [3], funcHighlight: [18], action: {type: 'highlight_memory', index: 2, color: '#00FF00'}, console: "Setting next pointer"},
{mainHighlight: [3], funcHighlight: [19], action: {type: 'add', index: 2, name: 'fred', number: '6173551212', color: '#0000FF'}, console: "Adding node to index 2"},
{mainHighlight: [3], funcHighlight: [20], action: {type: 'highlight_memory', index: 2, color: '#343434'}, console: "Returning from add function"},
{mainHighlight: [3], funcHighlight: [20], action: {type: 'highlight_person', index: 2, name: 'fred', number: '6173551212', color: '#222'}, console: "Returning from add function"},
// Add alice
{mainHighlight: [4], funcHighlight: [], action: null, console: "Adding entry: alice (5182761234)"},
{mainHighlight: [4], funcHighlight: [13], action: null, console: "Entering add function"},
{mainHighlight: [4], funcHighlight: [14], action: null, console: "Calculating hash index..."},
{mainHighlight: [4], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [4], funcHighlight: [11], action: null, console: "5182761234 % 10 = 4"},
{mainHighlight: [4], funcHighlight: [14], action: null, console: "Hash index: 4"},
{mainHighlight: [4], funcHighlight: [15], action: null, console: "Creating new node"},
{mainHighlight: [4], funcHighlight: [16], action: null, console: "Setting name: alice"},
{mainHighlight: [4], funcHighlight: [17], action: null, console: "Setting number: 5182761234"},
{mainHighlight: [4], funcHighlight: [18], action: {type: 'highlight_memory', index: 4, color: '#00FF00'}, console: "Setting next pointer"},
{mainHighlight: [4], funcHighlight: [19], action: {type: 'add', index: 4, name: 'alice', number: '5182761234', color: '#0000FF'}, console: "Adding node to index 4"},
{mainHighlight: [4], funcHighlight: [20], action: {type: 'highlight_memory', index: 4, color: '#343434'}, console: "Returning from add function"},
{mainHighlight: [4], funcHighlight: [20], action: {type: 'highlight_person', index: 4, name: 'alice', number: '5182761234', color: '#222'}, console: "Returning from add function"},
// Add carol
{mainHighlight: [5], funcHighlight: [], action: null, console: "Adding entry: carol (5182761267)"},
{mainHighlight: [5], funcHighlight: [13], action: null, console: "Entering add function"},
{mainHighlight: [5], funcHighlight: [14], action: null, console: "Calculating hash index..."},
{mainHighlight: [5], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [5], funcHighlight: [11], action: null, console: "5182761267 % 10 = 7"},
{mainHighlight: [5], funcHighlight: [14], action: null, console: "Hash index: 7"},
{mainHighlight: [5], funcHighlight: [15], action: null, console: "Creating new node"},
{mainHighlight: [5], funcHighlight: [16], action: null, console: "Setting name: carol"},
{mainHighlight: [5], funcHighlight: [17], action: null, console: "Setting number: 5182761267"},
{mainHighlight: [5], funcHighlight: [18], action: {type: 'highlight_memory', index: 7, color: '#00FF00'}, console: "Setting next pointer"},
{mainHighlight: [5], funcHighlight: [19], action: {type: 'add', index: 7, name: 'carol', number: '5182761267', color: '#0000FF'}, console: "Adding node to index 7"},
{mainHighlight: [5], funcHighlight: [20], action: {type: 'highlight_memory', index: 7, color: '#343434'}, console: "Returning from add function"},
{mainHighlight: [5], funcHighlight: [20], action: {type: 'highlight_person', index: 7, name: 'carol', number: '5182761267', color: '#222'}, console: "Returning from add function"},
// Add bob
{mainHighlight: [6], funcHighlight: [], action: null, console: "Adding entry: bob (5182765678)"},
{mainHighlight: [6], funcHighlight: [13], action: null, console: "Entering add function"},
{mainHighlight: [6], funcHighlight: [14], action: null, console: "Calculating hash index..."},
{mainHighlight: [6], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [6], funcHighlight: [11], action: null, console: "5182765678 % 10 = 8"},
{mainHighlight: [6], funcHighlight: [14], action: null, console: "Hash index: 8"},
{mainHighlight: [6], funcHighlight: [15], action: null, console: "Creating new node"},
{mainHighlight: [6], funcHighlight: [16], action: null, console: "Setting name: bob"},
{mainHighlight: [6], funcHighlight: [17], action: null, console: "Setting number: 5182765678"},
{mainHighlight: [6], funcHighlight: [18], action: {type: 'highlight_memory', index: 8, color: '#00FF00'}, console: "Setting next pointer"},
{mainHighlight: [6], funcHighlight: [19], action: {type: 'add', index: 8, name: 'bob', number: '5182765678', color: '#0000FF'}, console: "Adding node to index 8"},
{mainHighlight: [6], funcHighlight: [20], action: {type: 'highlight_memory', index: 8, color: '#343434'}, console: "Returning from add function"},
{mainHighlight: [6], funcHighlight: [20], action: {type: 'highlight_person', index: 8, name: 'bob', number: '5182765678', color: '#222'}, console: "Returning from add function"},
// Add erin
{mainHighlight: [7], funcHighlight: [], action: null, console: "Adding entry: erin (5182764488)"},
{mainHighlight: [7], funcHighlight: [13], action: null, console: "Entering add function"},
{mainHighlight: [7], funcHighlight: [14], action: null, console: "Calculating hash index..."},
{mainHighlight: [7], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [7], funcHighlight: [11], action: null, console: "5182764488 % 10 = 8"},
{mainHighlight: [7], funcHighlight: [14], action: null, console: "Hash index: 8"},
{mainHighlight: [7], funcHighlight: [15], action: null, console: "Creating new node"},
{mainHighlight: [7], funcHighlight: [16], action: null, console: "Setting name: erin"},
{mainHighlight: [7], funcHighlight: [17], action: null, console: "Setting number: 5182764488"},
{mainHighlight: [7], funcHighlight: [18], action: {type: 'highlight_memory', index: 8, color: '#00FF00'}, console: "Setting next pointer"},
{mainHighlight: [7], funcHighlight: [19], action: {type: 'add', index: 8, name: 'erin', number: '5182764488', chain: true, color: '#0000FF'}, console: "Adding node to index 8 (chaining)"},
{mainHighlight: [7], funcHighlight: [20], action: {type: 'highlight_memory', index: 8, color: '#343434'}, console: "Returning from add function"},
{mainHighlight: [7], funcHighlight: [20], action: {type: 'highlight_person', index: 8, name: 'erin', number: '5182764488', color: '#222'}, console: "Returning from add function"},
// Identify dan
{mainHighlight: [8], funcHighlight: [], action: null, console: "Searching for number: 5182764321"},
{mainHighlight: [8], funcHighlight: [21], action: null, console: "Entering identify function"},
{mainHighlight: [8], funcHighlight: [22], action: null, console: "Calculating hash index..."},
{mainHighlight: [8], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [8], funcHighlight: [11], action: null, console: "5182764321 % 10 = 1"},
{mainHighlight: [8], funcHighlight: [22], action: null, console: "Hash index: 1"},
{mainHighlight: [8], funcHighlight: [23], action: {type: 'highlight_memory', index: 1, color: '#00FF00'}, console: "Starting search at index 1"},
{mainHighlight: [8], funcHighlight: [24], action: null, console: "phonebook[1] is not nullptr(true)"},
{mainHighlight: [8], funcHighlight: [25], action: null, console: "5182764321 == 5182764321(true)"},
{mainHighlight: [8], funcHighlight: [26], action: {type: 'idan', index: 1, name: 'dan', color: '#0000FF'}, console: "Returning: dan"},
{mainHighlight: [8], funcHighlight: [31], action: {type: 'idan', index: 1, name: 'dan', color: '#222'}, console: "Returning from identify function"},
{mainHighlight: [8], funcHighlight: [], action: {type: 'highlight_memory', index: 1, color: '#343434'}, console: "Identify 5182764321: dan"},
// Identify erin
{mainHighlight: [9], funcHighlight: [], action: null, console: "Searching for number: 5182764488"},
{mainHighlight: [9], funcHighlight: [21], action: null, console: "Entering identify function"},
{mainHighlight: [9], funcHighlight: [22], action: null, console: "Calculating hash index..."},
{mainHighlight: [9], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [9], funcHighlight: [11], action: null, console: "5182764488 % 10 = 8"},
{mainHighlight: [9], funcHighlight: [22], action: null, console: "Hash index: 8"},
{mainHighlight: [9], funcHighlight: [23], action: {type: 'highlight_memory', index: 8, color: '#00FF00'}, console: "Starting search at index 8"},
{mainHighlight: [9], funcHighlight: [24], action: null, console: "phonebook[8] is not nullptr(true)"},
{mainHighlight: [9], funcHighlight: [25], action: null, console: "5182765678 != 5182764488(false)"},
{mainHighlight: [9], funcHighlight: [28], action: null, console: "Moving to next node"},
{mainHighlight: [9], funcHighlight: [24], action: null, console: "phonebook[8]->next is not nullptr(true)"},
{mainHighlight: [9], funcHighlight: [25], action: null, console: "5182764488 == 5182764488(true)"},
{mainHighlight: [9], funcHighlight: [26], action: {type: 'ierin', index: 8, name: 'erin', color: '#0000FF'}, console: "Returning: erin"},
{mainHighlight: [9], funcHighlight: [31], action: {type: 'ierin', index: 8, name: 'erin', color: '#222'}, console: "Returning from identify function"},
{mainHighlight: [9], funcHighlight: [], action: {type: 'highlight_memory', index: 8, color: '#343434'}, console: "Identify 5182764488: erin"},
// Identify fred
{mainHighlight: [10], funcHighlight: [], action: null, console: "Searching for number: 6173551212"},
{mainHighlight: [10], funcHighlight: [21], action: null, console: "Entering identify function"},
{mainHighlight: [10], funcHighlight: [22], action: null, console: "Calculating hash index..."},
{mainHighlight: [10], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [10], funcHighlight: [11], action: null, console: "6173551212 % 10 = 2"},
{mainHighlight: [10], funcHighlight: [22], action: null, console: "Hash index: 2"},
{mainHighlight: [10], funcHighlight: [23], action: {type: 'highlight_memory', index: 2, color: '#00FF00'}, console: "Starting search at index 2"},
{mainHighlight: [10], funcHighlight: [24], action: null, console: "phonebook[2] is not nullptr(true)"},
{mainHighlight: [10], funcHighlight: [25], action: null, console: "6173551212 == 6173551212(true)"},
{mainHighlight: [10], funcHighlight: [26], action: {type: 'ifred', index: 2, name: 'fred', color: '#0000FF'}, console: "Returning: fred"},
{mainHighlight: [10], funcHighlight: [31], action: {type: 'ifred', index: 2, name: 'fred', color: '#222'}, console: "Returning from identify function"},
{mainHighlight: [10], funcHighlight: [], action: {type: 'highlight_memory', index: 2, color: '#343434'}, console: "Identify 6173551212: fred"},
// Identify not found
{mainHighlight: [11], funcHighlight: [], action: null, console: "Searching for number: 1234567890"},
{mainHighlight: [11], funcHighlight: [21], action: null, console: "Entering identify function"},
{mainHighlight: [11], funcHighlight: [22], action: null, console: "Calculating hash index..."},
{mainHighlight: [11], funcHighlight: [10], action: null, console: "Entering hash_function"},
{mainHighlight: [11], funcHighlight: [11], action: null, console: "1234567890 % 10 = 0"},
{mainHighlight: [11], funcHighlight: [22], action: null, console: "Hash index: 0"},
{mainHighlight: [11], funcHighlight: [23], action: null, console: "Starting search at index 0"},
{mainHighlight: [11], funcHighlight: [24], action: null, console: "phonebook[0] is not nullptr(false)"},
{mainHighlight: [11], funcHighlight: [30], action: null, console: "Returning: Not found"},
{mainHighlight: [11], funcHighlight: [31], action: null, console: "Returning from identify function"},
{mainHighlight: [11], funcHighlight: [], action: null, console: "Identify 1234567890: Not found"},
// Return 0
{mainHighlight: [12], funcHighlight: [], action: null, console: "Program completed"}
];
function highlightCodeLines(mainLines, funcLines)
{
for (let i = 0; i < mainCodeTextNodes.length; ++i)
{
if (mainLines.includes(i))
{
mainCodeTextNodes[i].fontStyle('normal');
mainCodeTextNodes[i].fill('#FF0000'); // Bright red for current line
}
else
{
mainCodeTextNodes[i].fontStyle('normal');
mainCodeTextNodes[i].fill('#222');
}
}
for (let i = 0; i < funcCodeTextNodes.length; ++i)
{
if (funcLines.includes(i))
{
funcCodeTextNodes[i].fontStyle('normal');
funcCodeTextNodes[i].fill('#FF0000'); // Bright red for current line
}
else
{
funcCodeTextNodes[i].fontStyle('normal');
funcCodeTextNodes[i].fill('#222');
}
}
layer.draw();
}
let stepIndex = 0;
function nextstep()
{
if (stepIndex >= steps.length)
{
alert('End of animation! Refresh to restart.');
return;
}
let step = steps[stepIndex];
highlightCodeLines(step.mainHighlight, step.funcHighlight);
// Perform the action first
if (step.action)
{
if (step.action.type === 'add')
{
addNodeToMemory(step.action.index, step.action.name, step.action.number, step.action.chain, step.action.color);
}
else if (step.action.type === 'highlight_memory')
{
highlightMemoryBox(step.action.index, step.action.color);
}
else if (step.action.type === 'idan')
{
highlightPersonBox(step.action.index, step.action.name, 5182764321, step.action.color);
}
else if (step.action.type === 'ierin')
{
highlightPersonBox(step.action.index, step.action.name, 5182764488, step.action.color);
}
else if (step.action.type === 'ifred')
{
highlightPersonBox(step.action.index, step.action.name, 6173551212, step.action.color);
}
else if (step.action.type === 'highlight_person')
{
highlightPersonBox(step.action.index, step.action.name, step.action.number, step.action.color);
}
}
// Then show the console message
if (step.console)
{
addConsoleText(step.console);
}
layer.draw();
++stepIndex;
}
window.nextstep = nextstep;
function highlightNext(index, path, i)
{
if (i < path.length)
{
let obj = memoryNodes[index][path[i]];
if (obj)
{
obj.group.children.each(child => {
if (child instanceof Konva.Rect)
{
child.stroke('#FF0000'); // red for current
child.strokeWidth(5);
}
});
}
layer.draw();
++i;
if (i < path.length)
{
setTimeout(() => highlightNext(index, path, i), 1000);
}
}
}
function highlightIdentifyPath(index, path)
{
// Remove previous highlights
for (let idx in memoryNodes)
{
memoryNodes[idx].forEach(obj => {
obj.group.children.each(child => {
if (child instanceof Konva.Rect)
{
child.stroke('#222');
child.strokeWidth(2);
}
});
});
}
if (!memoryNodes[index]) return;
// Start the animation
highlightNext(index, path, 0);
}
// --- Main Code Box (left) ---
const mainCodeLines =
[
'int main(){',
' Node *phonebook[SIZE] = {nullptr};',
' add(phonebook, 5182764321, "dan");',
' add(phonebook, 6173551212, "fred");',
' add(phonebook, 5182761234, "alice");',
' add(phonebook, 5182761267, "carol");',
' add(phonebook, 5182765678, "bob");',
' add(phonebook, 5182764488, "erin");',
' std::cout << "Identify 5182764321: " << identify(phonebook, 5182764321) << std::endl;',
' std::cout << "Identify 5182764488: " << identify(phonebook, 5182764488) << std::endl;',
' std::cout << "Identify 6173551212: " << identify(phonebook, 6173551212) << std::endl;',
' std::cout << "Identify 1234567890: " << identify(phonebook, 1234567890) << std::endl;',
' return 0;',
'}'
];
// --- Function Code Box (center) ---
const funcCodeLines =
[
'#include <iostream>',
'#include <string>',
'const int SIZE = 10;',
'struct Node{',
'public:',
' int number;',
' std::string name;',
' Node *next;',
' Node() : name(""), number(0), next(nullptr) {}',
'};',
'int hash_function(int number){',
' return number % SIZE;',
'}',
'void add(Node *phonebook[SIZE], int number, std::string &name){',
' int index = hash_function(number);',
' Node *tmp = new Node;',
' tmp->name = name;',
' tmp->number = number;',
' tmp->next = phonebook[index];',
' phonebook[index] = tmp;',
'}',
'std::string identify(Node *phonebook[SIZE], int number){',
' int index = hash_function(number);',
' Node *curr = phonebook[index];',
' while (curr != nullptr){',
' if (curr->number == number){',
' return curr->name;',
' }',
' curr = curr->next;',
' }',
' return "Not found";',
'}'
];
// Draw main code box (left)
var mainCodeBox = new Konva.Rect
({
x: 20,
y: 25,
width: 800,
height: mainCodeLines.length * 24 + 40,
fill: '#ddd',
stroke: '#555',
strokeWidth: 5,
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
cornerRadius: 10,
});
layer.add(mainCodeBox);
// Draw console box under main code box
var consoleBox = new Konva.Rect
({
x: 20,
y: mainCodeBox.y() + mainCodeBox.height() + 40,
width: 800,
height: 100,
fill: '#ddd',
stroke: '#555',
strokeWidth: 5,
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
cornerRadius: 10,
});
layer.add(consoleBox);
var consoleLabel = new Konva.Text
({
x: 375,
y: consoleBox.y() - 25,
id: 'console',
text: 'Console',
fontSize: 28,
fontFamily: 'Calibri',
fill: '#000000',
});
layer.add(consoleLabel);
let consoleTextNode = null;
function addConsoleText(text)
{
if (consoleTextNode)
{
consoleTextNode.destroy();
}
consoleTextNode = new Konva.Text
({
x: 30,
y: consoleBox.y() + 30,
text: text,
id: 'console_line',
fontSize: 16,
fontFamily: 'Consolas',
fill: '#000000',
width: 785,
padding: 1,
wrap: 'none',
});
layer.add(consoleTextNode);
layer.draw();
}
let mainCodeTextNodes = [];
for (let i = 0; i < mainCodeLines.length; ++i)
{
let t = new Konva.Text
({
x: 30,
y: 40 + i * 24,
text: mainCodeLines[i],
id: 'main_code_line_' + i,
fontSize: 16,
fontFamily: 'Consolas',
fill: '#222',
width: 785,
padding: 1,
wrap: 'none',
});
mainCodeTextNodes.push(t);
layer.add(t);
}
// Function code box (center)
var funcCodeBox = new Konva.Rect
({
x: 850,
y: 25,
width: 600,
height: funcCodeLines.length * 24 + 30,
fill: '#ddd',
stroke: '#555',
strokeWidth: 5,
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
cornerRadius: 10,
});
layer.add(funcCodeBox);
var funcCodeLabel = new Konva.Text
({
x: 1175,
y: 0,
id: 'func_code',
text: 'Functions',
fontSize: 28,
fontFamily: 'Calibri',
fill: '#000000',
});
layer.add(funcCodeLabel);
let funcCodeTextNodes = [];
for (let i = 0; i < funcCodeLines.length; ++i)
{
let t = new Konva.Text
({
x: 875,
y: 40 + i * 24,
text: funcCodeLines[i],
id: 'func_code_line_' + i,
fontSize: 16,
fontFamily: 'Consolas',
fill: '#222',
width: 750,
padding: 1,
wrap: 'none',
});
funcCodeTextNodes.push(t);
layer.add(t);
}
let memoryNodes = {};
function addNodeToMemory(index, name, number, chain = false, color = '#222')
{
if (!memoryNodes[index]) memoryNodes[index] = [];
let y = 60 + index * 50 + 10;
let x = 1625 + memoryNodes[index].length * 150;
// Draw node group (rectangle + text)
let group = new Konva.Group();
let nodeRect = new Konva.Rect
({
x: x,
y: y,
width: 100,
height: 30,
fill: '#fff',
stroke: color,
strokeWidth: 4,
cornerRadius: 5,
});
let numberText = new Konva.Text
({
x: x + 5,
y: y + 2,
text: String(number),
fontSize: 12,
fontFamily: 'Consolas',
fill: '#333',
});
let nameText = new Konva.Text
({
x: x + 5,
y: y + 15,
text: name,
fontSize: 15,
fontFamily: 'Calibri',
fill: '#000',
fontStyle: 'bold',
});
group.add(nodeRect);
group.add(numberText);
group.add(nameText);
layer.add(group);
// Draw arrow from memory box or previous node
let arrow;
if (memoryNodes[index].length === 0)
{
arrow = new Konva.Arrow
({
points: [1600, y + 15, x, y + 15],
pointerLength: 10,
pointerWidth: 10,
fill: 'black',
stroke: 'black',
strokeWidth: 2,
});
}
else
{
let prevX = 1625 + (memoryNodes[index].length - 1) * 150 + 100;
arrow = new Konva.Arrow
({
points: [prevX, y + 15, x, y + 15],
pointerLength: 10,
pointerWidth: 10,
fill: 'black',
stroke: 'black',
strokeWidth: 2,
});
}
layer.add(arrow);
memoryNodes[index].push({group, arrow});
}
function highlightMemoryBox(index, color)
{
// Find the memory box by its ID
const memoryBox = layer.findOne('#brec' + index);
if (memoryBox)
{
memoryBox.stroke(color);
memoryBox.strokeWidth(5);
layer.draw();
}
}
function highlightPersonBox(index, name, number, color)
{
let y = 60 + index * 50 + 10;
let x = 1625 + (memoryNodes[index].length - 1) * 150;
// Draw node group (rectangle + text)
let group = new Konva.Group();
let nodeRect = new Konva.Rect
({
x: x,
y: y,
width: 100,
height: 30,
fill: '#fff',
stroke: color,
strokeWidth: 4,
cornerRadius: 5,
});
let numberText = new Konva.Text
({
x: x + 5,
y: y + 2,
text: String(number),
fontSize: 12,
fontFamily: 'Consolas',
fill: '#333',
});
let nameText = new Konva.Text
({
x: x + 5,
y: y + 15,
text: name,
fontSize: 15,
fontFamily: 'Calibri',
fill: '#000',
fontStyle: 'bold',
});
group.add(nodeRect);
group.add(numberText);
group.add(nameText);
layer.add(group);
layer.draw();
}

View File

@@ -0,0 +1,504 @@
// initial array to be sorted
let array = [5, 3, 8, 1, 9, 4];
let currentStep = 0;
let animationSteps = [];
// heap sort code
const codeLines = [
"void heapSort(int arr[], int n) {",
" for (int i = n / 2 - 1; i >= 0; i--)",
" heapify(arr, n, i);",
" for (int i = n - 1; i > 0; i--) {",
" std::swap(arr[0], arr[i]);",
" heapify(arr, i, 0);",
" }",
"}",
"",
"void heapify(int arr[], int n, int i) {",
" int largest = i;",
" int left = 2 * i + 1;",
" int right = 2 * i + 2;",
" if (left < n && arr[left] > arr[largest])",
" largest = left;",
" if (right < n && arr[right] > arr[largest])",
" largest = right;",
" if (largest != i) {",
" std::swap(arr[i], arr[largest]);",
" heapify(arr, n, largest);",
" }",
"}"
];
function initialize() {
const codeElement = document.getElementById('code');
let codeHTML = '';
codeLines.forEach((line, index) => {
// how much padding to add
const paddingLeft = line.startsWith(' ') ? '20px' : '0';
// add the line number for the code
codeHTML += `<div id="code-line-${index}">`;
if (!line.trim()) {
codeHTML += '&nbsp;';
} else {
const lineNum = index;
// align single digit lines better
const formattedLineNum = lineNum < 10 ? ` ${lineNum}` : lineNum;
codeHTML += `${formattedLineNum}. ${line}`;
}
codeHTML += '</div>';
});
codeElement.innerHTML = codeHTML;
generateAnimationSteps();
// initial array display
updateArrayDisplay();
// initial heap tree
drawHeapTree();
// set up next step buttons
document.getElementById('arrayNextBtn').addEventListener('click', nextStep);
document.getElementById('treeNextBtn').addEventListener('click', nextStep);
}
// highlight a specific line of code
function highlightLine(lineIndex) {
// reset all lines
for (let i = 0; i < codeLines.length; i++) {
const line = document.getElementById(`code-line-${i}`);
if (line) {
line.style.backgroundColor = 'transparent';
line.style.fontWeight = 'normal';
}
}
// highlight the specified line
const highlightedLine = document.getElementById(`code-line-${lineIndex}`);
if (highlightedLine) {
highlightedLine.style.fontWeight = 'bold';
}
}
// update the array display
function updateArrayDisplay() {
const arrayContainer = document.getElementById('arrayContainer');
const descriptionElement = document.getElementById('arrayDescription');
arrayContainer.innerHTML = '';
// create array visualization
array.forEach((value, index) => {
const elementContainer = document.createElement('div');
elementContainer.style.display = 'inline-block';
elementContainer.style.marginRight = '5px';
const element = document.createElement('div');
element.className = 'array-element';
// highlight right lines, and nodes based on current step
if (animationSteps[currentStep] && animationSteps[currentStep].comparing && animationSteps[currentStep].comparing.includes(index)) {
element.classList.add('highlight');
} else if (animationSteps[currentStep] && animationSteps[currentStep].swapping && animationSteps[currentStep].swapping.includes(index)) {
element.classList.add('swap');
} else if (animationSteps[currentStep] && animationSteps[currentStep].sorted && animationSteps[currentStep].sorted.includes(index)) {
element.classList.add('done');
} else if (animationSteps[currentStep] && animationSteps[currentStep].activeHeap && index < animationSteps[currentStep].heapSize) {
element.classList.add('active-heap');
}
element.textContent = value;
elementContainer.appendChild(element);
// add index below
const indexElement = document.createElement('div');
indexElement.className = 'array-index';
indexElement.textContent = index;
elementContainer.appendChild(indexElement);
arrayContainer.appendChild(elementContainer);
});
// update the description
if (animationSteps[currentStep] && animationSteps[currentStep].description) {
descriptionElement.textContent = animationSteps[currentStep].description;
} else {
descriptionElement.textContent = '';
}
}
// draw heap tree on the canvas
function drawHeapTree() {
const canvas = document.getElementById('treeCanvas');
// canvas with context
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
const heapSize = animationSteps[currentStep] ? (animationSteps[currentStep].heapSize || array.length) : array.length;
// tree node positions (x, y coordinates for each node)
const positions = [
[250, 50], // index 0
[150, 120], // index 1
[350, 120], // index 2
[100, 190], // index 3
[200, 190], // index 4
[300, 190], // index 5
];
// draw edges/arrows
for (let i = 0; i < Math.min(array.length, positions.length); i++) {
if (i > 0 && i < heapSize) {
const parentIndex = Math.floor((i - 1) / 2);
if (parentIndex < heapSize) {
// starting point for the line from parent to child
const startX = positions[parentIndex][0];
// bottom of parent node
const startY = positions[parentIndex][1] + 20;
const endX = positions[i][0];
// top of child node
const endY = positions[i][1] - 20;
// draw the lines for the arrows
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.stroke();
// draw arrows
const angle = Math.atan2(endY - startY, endX - startX);
const arrowSize = 10;
ctx.beginPath();
ctx.moveTo(endX, endY);
ctx.lineTo(
endX - arrowSize * Math.cos(angle - Math.PI / 6),
endY - arrowSize * Math.sin(angle - Math.PI / 6)
);
ctx.lineTo(
endX - arrowSize * Math.cos(angle + Math.PI / 6),
endY - arrowSize * Math.sin(angle + Math.PI / 6)
);
ctx.closePath();
ctx.fillStyle = '#555';
ctx.fill();
}
}
}
for (let i = 0; i < Math.min(array.length, positions.length); i++) {
if (i < heapSize) {
// determine node color based on current operation
let nodeColor = '#ddd';
let borderColor = '#888';
if (animationSteps[currentStep] && animationSteps[currentStep].comparing &&
animationSteps[currentStep].comparing.includes(i)) {
nodeColor = '#ffcf4d';
borderColor = '#e8a800';
} else if (animationSteps[currentStep] && animationSteps[currentStep].swapping &&
animationSteps[currentStep].swapping.includes(i)) {
nodeColor = '#ff7675';
borderColor = '#d63031';
} else if (animationSteps[currentStep] && animationSteps[currentStep].sorted &&
animationSteps[currentStep].sorted.includes(i)) {
nodeColor = '#81ecec';
borderColor = '#00cec9';
} else if (animationSteps[currentStep] && animationSteps[currentStep].activeHeap && i < animationSteps[currentStep].heapSize) {
// highlight active heap nodes
nodeColor = '#a3d8f4';
borderColor = '#0984e3';
}
// draw rectangle with round corners
const width = 40;
const height = 40;
const x = positions[i][0] - width/2;
const y = positions[i][1] - height/2;
const radius = 5;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
// style rectangle
ctx.fillStyle = nodeColor;
ctx.fill();
ctx.strokeStyle = borderColor;
ctx.lineWidth = 2;
ctx.stroke();
// draw text in the heap tree
ctx.fillStyle = 'black';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = 'bold 16px Times New Roman';
ctx.fillText(array[i].toString(), positions[i][0], positions[i][1]);
}
}
}
function generateAnimationSteps() {
animationSteps = [];
const originalArray = [5, 3, 8, 1, 9, 4];
let tempArray = [...originalArray];
const n = tempArray.length;
animationSteps.push({
array: [...originalArray],
line: 0,
description: 'Starting heap sort with array [' + originalArray.join(', ') + ']',
heapSize: n
});
// build max-heap
animationSteps.push({
array: [...originalArray],
line: 2,
description: 'Building max heap',
heapSize: n
});
let sortedIndices = [];
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
animationSteps.push({
array: [...originalArray],
line: 3,
description: `Heapifying subtree rooted at index ${i}`,
heapSize: n,
comparing: [i]
});
heapifyWithAnimation(tempArray, n, i, sortedIndices, originalArray);
}
animationSteps.push({
array: [...originalArray],
line: 6,
description: 'Max heap built. Extracting elements one by one',
heapSize: n
});
for (let i = n - 1; i > 0; i--) {
// highlight the active heap portion before swap
animationSteps.push({
array: [...originalArray],
line: 7,
description: `Current active heap size: ${i+1}. Elements from index 0 to ${i} need to be processed.`,
heapSize: i + 1,
activeHeap: true,
sorted: [...sortedIndices]
});
animationSteps.push({
array: [...originalArray],
line: 8,
description: `Swapping root (${tempArray[0]}) with element at index ${i} (${tempArray[i]})`,
heapSize: i + 1,
swapping: [0, i]
});
[tempArray[0], tempArray[i]] = [tempArray[i], tempArray[0]];
// this index is sorted
sortedIndices.push(i);
animationSteps.push({
array: [...originalArray],
line: 8,
description: `Element ${tempArray[i]} is now in its correct position`,
heapSize: i,
sorted: [...sortedIndices]
});
animationSteps.push({
array: [...originalArray],
line: 11,
description: `Heapifying reduced heap (size ${i})`,
heapSize: i,
activeHeap: true,
sorted: [...sortedIndices]
});
heapifyWithAnimation(tempArray, i, 0, sortedIndices, originalArray);
}
sortedIndices.push(0);
animationSteps.push({
array: [...originalArray],
line: 13,
description: 'Heap sort complete! Array is now sorted: [' + tempArray.join(', ') + ']',
heapSize: n,
sorted: sortedIndices
});
}
// heapify function that records animation steps
function heapifyWithAnimation(arr, n, i, sortedIndices, originalArray) {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
animationSteps.push({
array: [...originalArray],
line: 16,
description: `Heapifying at index ${i}`,
heapSize: n,
comparing: [i],
sorted: sortedIndices ? [...sortedIndices] : []
});
// compare with left child
if (left < n) {
animationSteps.push({
array: [...originalArray],
line: 21,
description: `Comparing ${arr[i]} (index ${i}) with left child ${arr[left]} (index ${left})`,
heapSize: n,
comparing: [i, left],
sorted: sortedIndices ? [...sortedIndices] : []
});
if (arr[left] > arr[largest]) {
largest = left;
animationSteps.push({
array: [...originalArray],
line: 22,
description: `Left child ${arr[left]} is larger than current largest ${arr[i]}`,
heapSize: n,
comparing: [largest],
sorted: sortedIndices ? [...sortedIndices] : []
});
} else {
animationSteps.push({
array: [...originalArray],
line: 21,
description: `No action needed: ${arr[i]} (index ${i}) is already larger than or equal to its left child ${arr[left]} (index ${left})`,
heapSize: n,
comparing: [i],
sorted: sortedIndices ? [...sortedIndices] : []
});
}
} else {
// no left child
animationSteps.push({
array: [...originalArray],
line: 21,
description: `No left child exists for node at index ${i}`,
heapSize: n,
comparing: [i],
sorted: sortedIndices ? [...sortedIndices] : []
});
}
// compare with right child
if (right < n) {
animationSteps.push({
array: [...originalArray],
line: 25,
description: `Comparing ${arr[largest]} (index ${largest}) with right child ${arr[right]} (index ${right})`,
heapSize: n,
comparing: [largest, right],
sorted: sortedIndices ? [...sortedIndices] : []
});
if (arr[right] > arr[largest]) {
largest = right;
animationSteps.push({
array: [...originalArray],
line: 26,
description: `Right child ${arr[right]} is larger than current largest ${arr[largest === i ? arr[i] : arr[left]]}`,
heapSize: n,
comparing: [largest],
sorted: sortedIndices ? [...sortedIndices] : []
});
} else {
animationSteps.push({
array: [...originalArray],
line: 25,
description: `No action needed: ${arr[largest]} (index ${largest}) is already larger than or equal to its right child ${arr[right]} (index ${right})`,
heapSize: n,
comparing: [largest],
sorted: sortedIndices ? [...sortedIndices] : []
});
}
} else {
// there is no right child
animationSteps.push({
array: [...originalArray],
line: 25,
description: `No right child exists for node at index ${i}`,
heapSize: n,
comparing: [i],
sorted: sortedIndices ? [...sortedIndices] : []
});
}
// if largest is not root, swap and heapify
if (largest !== i) {
animationSteps.push({
array: [...originalArray],
line: 30,
description: `Swapping ${arr[i]} (index ${i}) with ${arr[largest]} (index ${largest})`,
heapSize: n,
swapping: [i, largest],
sorted: sortedIndices ? [...sortedIndices] : []
});
[arr[i], arr[largest]] = [arr[largest], arr[i]];
animationSteps.push({
array: [...originalArray],
line: 33,
description: `Recursively heapifying the affected subtree rooted at index ${largest}`,
heapSize: n,
comparing: [largest],
sorted: sortedIndices ? [...sortedIndices] : []
});
heapifyWithAnimation(arr, n, largest, sortedIndices, originalArray);
} else {
// add explanation when no swap is needed
animationSteps.push({
array: [...originalArray],
line: 30,
description: `No swap needed: Element at index ${i} (${arr[i]}) is already in correct position for max heap`,
heapSize: n,
comparing: [i],
sorted: sortedIndices ? [...sortedIndices] : []
});
}
}
function nextStep() {
if (currentStep < animationSteps.length - 1) {
currentStep++;
// always use the original array for display
if (animationSteps[currentStep] && animationSteps[currentStep].array) {
array = [...animationSteps[currentStep].array];
}
// highlight code line
if (animationSteps[currentStep] && animationSteps[currentStep].line !== undefined) {
highlightLine(animationSteps[currentStep].line);
}
// update visuals
updateArrayDisplay();
drawHeapTree();
}
}
// initialize on page load
window.onload = initialize;

View File

@@ -0,0 +1,142 @@
<!-- AUTHOR: Chloe Lee -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Heap Sort Visualization</title>
<script src="../../konva.js"></script>
<style>
body {
font-family: 'Times New Roman', Times, serif, sans-serif;
padding: 20px;
background-color: white;
}
h1 {
font-size: 24px;
margin-bottom: 10px;
}
p {
margin-bottom: 20px;
}
.container {
display: flex;
flex-direction: column;
gap: 30px;
max-width: 1200px;
margin: 0 auto;
}
.code-block {
background-color: #e6e6e6;
border-radius: 10px;
padding: 20px;
box-shadow: 5px 5px 10px rgba(0,0,0,0.2);
width: 100%;
max-width: 600px;
margin: 0 auto;
}
.visualization-container {
display: flex;
justify-content: space-between;
gap: 20px;
}
.visualization-box {
background-color: #e6e6e6;
border-radius: 10px;
padding: 20px;
box-shadow: 5px 5px 10px rgba(0,0,0,0.2);
flex: 1;
min-height: 200px;
position: relative;
}
button.next-step {
font-family: 'Times New Roman', Times, serif, sans-serif;
margin: 0;
padding: 5px 10px;
font-size: 14px;
background-color: #f0f0f0;
border: 1px solid #999;
border-radius: 3px;
cursor: pointer;
position: absolute;
left: 20px;
top: 20px;
}
.array-container {
display: flex;
justify-content: center;
margin-top: 60px;
}
.array-element {
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
background-color: #ddd;
border: 2px solid #888;
border-radius: 5px;
margin: 0 2px;
font-weight: bold;
}
.array-index {
text-align: center;
margin-top: 5px;
font-size: 14px;
}
.highlight {
background-color: #ffcf4d;
border-color: #e8a800;
}
.swap {
background-color: #ff7675;
border-color: #d63031;
}
.done {
background-color: #81ecec;
border-color: #00cec9;
}
.description {
margin-top: 20px;
text-align: center;
font-size: 14px;
height: 40px;
}
#code{
margin: 0;
font-family: monospace;
font-size: 14px;
}
#treeCanvas{
margin-top: 30px;
}
</style>
</head>
<body>
<div class="container">
<div>
<h1>Heap Sort Visualization</h1>
<p>This animation shows how the heap sort algorithm works by building a max heap and then extracting elements one by one. Click the "next step" button to run the animation.</p>
</div>
<!-- block of C++ code-->
<div class="code-block" id="code-block">
<pre id="code"></pre>
</div>
<div class="visualization-container">
<!-- array goes here -->
<div class="visualization-box">
<button id="arrayNextBtn" class="next-step">Next Step</button>
<div id="arrayContainer" class="array-container"></div>
<div id="arrayDescription" class="description"></div>
</div>
<!-- heap tree goes here -->
<div class="visualization-box">
<button id="treeNextBtn" class="next-step">Next Step</button>
<canvas id="treeCanvas" width="500" height="300"></canvas>
</div>
</div>
</div>
<script src="heap-sort.js"></script>
</body>
</html>

View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Contains Nearby Duplicate Visualization</title>
<link rel="stylesheet" href="style.css">
<!-- Load Konva.js -->
<script src="https://cdn.jsdelivr.net/npm/konva@8.3.13/konva.min.js"></script>
<script src="script.js" defer></script>
</head>
<body>
<h1>Contains Nearby Duplicate Using std::map</h1>
<p>This visualization demonstrates the algorithm that checks if an array contains duplicates within distance k.</p>
<div class="visualization-container">
<div class="left-panel">
<div class="code-container">
<pre><code id="codeDisplay"></code></pre>
</div>
<div class="explanation-result-container">
<div class="explanation" id="explanation">
Select a test case and click "Next Step" to begin the animation.
</div>
<div class="result-container" id="resultContainer">
Result: <span id="result"></span>
</div>
</div>
</div>
<div class="visualization-panel">
<div class="case-description" id="caseDescription"></div>
<div class="top-controls">
<div class="test-case-btns">
<button class="test-btn active" data-testcase="0">Test Case 1</button>
<button class="test-btn" data-testcase="1">Test Case 2</button>
<button class="test-btn" data-testcase="2">Test Case 3</button>
<button class="test-btn" data-testcase="3">Test Case 4</button>
<button class="test-btn" data-testcase="4">Test Case 5</button>
<button class="test-btn" data-testcase="5">Test Case 6</button>
</div>
</div>
<div class="controls">
<button id="nextStep">Next Step</button>
<button id="resetBtn">Reset</button>
</div>
<div class="visualization-area">
<div class="array-container" id="arrayContainer"></div>
<div class="map-container" id="mapContainer"></div>
<div class="color-key">
<div class="color-key-label">Color Key:</div>
<div class="key-item">
<div class="key-color key-current"></div>
<div class="key-description">Current Element</div>
</div>
<div class="key-item">
<div class="key-color key-found"></div>
<div class="key-description">Previous Occurrence</div>
</div>
<div class="key-item">
<div class="key-color key-duplicate"></div>
<div class="key-description">Duplicate Within Distance k</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

322
animations/maps/script.js Normal file
View File

@@ -0,0 +1,322 @@
// Define code content with syntax highlighting
const codeContent = [
'<span class="line-number">0.</span> <span style="color:blue">bool</span> containsNearbyDuplicate(<span style="color:blue">std::vector</span>&lt;<span style="color:blue">int</span>&gt;&amp; nums, <span style="color:blue">int</span> k) {',
'<span class="line-number">1.</span> <span style="color:blue">int</span> size = nums.size();',
'<span class="line-number">2.</span> <span style="color:green">// create the map, map key is the value of the vector element, map value is the index</span>',
'<span class="line-number">3.</span> <span style="color:blue">std::map</span>&lt;<span style="color:blue">int</span>,<span style="color:blue">int</span>&gt; map1;',
'<span class="line-number">4.</span> <span style="color:blue">for</span>(<span style="color:blue">int</span> i=0; i&lt;size; i++) {',
'<span class="line-number">5.</span> <span style="color:green">// if already exists</span>',
'<span class="line-number">6.</span> <span style="color:blue">if</span>(map1.find(nums[i]) != map1.end()) {',
'<span class="line-number">7.</span> <span style="color:blue">if</span>(i - map1[nums[i]] &lt;= k) {',
'<span class="line-number">8.</span> <span style="color:blue">return true</span>;',
'<span class="line-number">9.</span> }',
'<span class="line-number">10.</span> }',
'<span class="line-number">11.</span> map1[nums[i]] = i;',
'<span class="line-number">12.</span> }',
'<span class="line-number">13.</span> <span style="color:blue">return false</span>;',
'<span class="line-number">14.</span> }'
];
// Test cases
const testCases = [
{
array: [1, 2, 3, 1],
k: 3,
description: "Array [1, 2, 3, 1] with k=3: Expected true (nums[0] == nums[3], |0-3| <= 3)"
},
{
array: [1, 0, 1, 1],
k: 1,
description: "Array [1, 0, 1, 1] with k=1: Expected true (nums[2] == nums[3], |2-3| <= 1)"
},
{
array: [1, 2, 3, 4, 5],
k: 2,
description: "Array [1, 2, 3, 4, 5] with k=2: Expected false (no duplicates within distance k)"
},
{
array: [1, 2, 3, 4, 1],
k: 4,
description: "Array [1, 2, 3, 4, 1] with k=4: Expected true (nums[0] == nums[4], |0-4| == 4 <= 4)"
},
{
array: [99, 99],
k: 1,
description: "Array [99, 99] with k=1: Expected true (nums[0] == nums[1], |0-1| <= 1)"
},
{
array: [1, 2, 3, 4, 5, 6],
k: 2,
description: "Array [1, 2, 3, 4, 5, 6] with k=2: Expected false (no duplicates)"
}
];
// Global variables to track animation state
let currentTestCase = 0;
let currentStep = 0;
let map = new Map();
let result = false;
let animationFinished = false;
let currentLineHighlighted = -1;
// Initialize code display
function initCodeDisplay() {
const codeDisplay = document.getElementById('codeDisplay');
codeContent.forEach((line, index) => {
const lineDiv = document.createElement('div');
lineDiv.className = 'code-line';
lineDiv.setAttribute('data-line', index);
lineDiv.innerHTML = line;
codeDisplay.appendChild(lineDiv);
});
}
// Highlight specific line of code
function highlightLine(lineNum) {
// Remove current highlight
if (currentLineHighlighted >= 0) {
const currentLine = document.querySelector(`.code-line[data-line="${currentLineHighlighted}"]`);
if (currentLine) {
currentLine.classList.remove('highlighted');
}
}
// Add new highlight
currentLineHighlighted = lineNum;
if (lineNum >= 0) {
const targetLine = document.querySelector(`.code-line[data-line="${lineNum}"]`);
if (targetLine) {
targetLine.classList.add('highlighted');
targetLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
// Update test case description
function updateCaseDescription() {
const descEl = document.getElementById('caseDescription');
descEl.textContent = testCases[currentTestCase].description;
}
// Create array visualization
function createArrayVisualization() {
const container = document.getElementById('arrayContainer');
container.innerHTML = ''; // Clear container
const array = testCases[currentTestCase].array;
array.forEach((value, index) => {
const item = document.createElement('div');
item.className = 'array-item';
item.setAttribute('data-index', index);
item.textContent = value;
container.appendChild(item);
});
}
// Update map visualization
function updateMapVisualization() {
const container = document.getElementById('mapContainer');
container.innerHTML = ''; // Clear container
// Display current map entries
map.forEach((value, key) => {
const item = document.createElement('div');
item.className = 'map-item';
const keyDiv = document.createElement('div');
keyDiv.className = 'map-key';
keyDiv.textContent = `Key: ${key}`;
const valueDiv = document.createElement('div');
valueDiv.className = 'map-value';
valueDiv.textContent = `Index: ${value}`;
item.appendChild(keyDiv);
item.appendChild(valueDiv);
container.appendChild(item);
});
}
// Update explanation text
function updateExplanation(text) {
document.getElementById('explanation').textContent = text;
}
// Update result display
function updateResult(res, final = false) {
const resultEl = document.getElementById('result');
resultEl.textContent = res ? "true" : "false";
if (final) {
resultEl.className = res ? "success" : "failure";
} else {
resultEl.className = "";
}
}
// Highlight array item
function highlightArrayItem(index, className) {
const items = document.querySelectorAll('.array-item');
if (index >= 0 && index < items.length) {
// Remove previous highlighting of the same class
items.forEach(item => {
if (item.classList.contains(className)) {
item.classList.remove(className);
}
});
// Add new highlight
items[index].classList.add(className);
}
}
// Remove all highlights from array items
function clearArrayHighlights() {
const items = document.querySelectorAll('.array-item');
items.forEach(item => {
item.classList.remove('current', 'found', 'duplicate');
});
}
// Reset animation state
function resetAnimation() {
currentStep = 0;
map = new Map();
result = false;
animationFinished = false;
highlightLine(-1);
clearArrayHighlights();
updateMapVisualization();
updateResult(false);
updateExplanation("Animation reset. Click 'Next Step' to begin.");
document.getElementById('nextStep').disabled = false;
}
// Handle test case button clicks
function initTestCaseButtons() {
const buttons = document.querySelectorAll('.test-btn');
buttons.forEach(button => {
button.addEventListener('click', function() {
// Update active button
buttons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
// Set current test case
currentTestCase = parseInt(this.getAttribute('data-testcase'));
// Reset and initialize visualization
resetAnimation();
updateCaseDescription();
createArrayVisualization();
});
});
}
// Animation steps for containsNearbyDuplicate algorithm
async function runAnimationStep() {
const array = testCases[currentTestCase].array;
const k = testCases[currentTestCase].k;
if (animationFinished) {
updateExplanation("Animation complete. Choose another test case or click 'Reset' to run again.");
return;
}
switch (currentStep) {
case 0:
// Function definition
highlightLine(0);
updateExplanation(`Starting containsNearbyDuplicate function with array [${array.join(', ')}] and k=${k}`);
break;
case 1:
// Get array size
highlightLine(1);
updateExplanation(`Getting array size: size = ${array.length}`);
break;
case 2:
// Create map
highlightLine(3);
updateExplanation("Creating an empty map to store values and their indices");
break;
default:
// Handle main loop iterations
const loopIndex = Math.floor((currentStep - 3) / 3);
const stepInLoop = (currentStep - 3) % 3;
if (loopIndex >= array.length) {
// Loop finished, return result
highlightLine(13);
updateExplanation("No duplicates found within distance k. Returning false.");
updateResult(false, true);
animationFinished = true;
break;
}
const currentValue = array[loopIndex];
if (stepInLoop === 0) {
// Start of loop iteration
highlightLine(4);
updateExplanation(`Starting iteration ${loopIndex}: Processing element nums[${loopIndex}] = ${currentValue}`);
clearArrayHighlights();
highlightArrayItem(loopIndex, 'current');
}
else if (stepInLoop === 1) {
// Check if value exists in map
highlightLine(6);
const exists = map.has(currentValue);
updateExplanation(`Checking if ${currentValue} already exists in the map: ${exists ? 'Found' : 'Not found'}`);
if (exists) {
const prevIndex = map.get(currentValue);
highlightArrayItem(prevIndex, 'found');
// Check if within k distance
highlightLine(7);
const diff = loopIndex - prevIndex;
const withinK = diff <= k;
if (withinK) {
// Found duplicate within k distance
highlightLine(8);
updateExplanation(`Found duplicate! nums[${prevIndex}] = nums[${loopIndex}] = ${currentValue}, and |${loopIndex} - ${prevIndex}| = ${diff} <= ${k}. Returning true.`);
updateResult(true, true);
highlightArrayItem(loopIndex, 'duplicate');
animationFinished = true;
break;
} else {
updateExplanation(`nums[${prevIndex}] = nums[${loopIndex}] = ${currentValue}, but |${loopIndex} - ${prevIndex}| = ${diff} > ${k}. Continue checking.`);
}
}
}
else if (stepInLoop === 2) {
// Update map
highlightLine(11);
map.set(currentValue, loopIndex);
updateMapVisualization();
updateExplanation(`Updating map: map[${currentValue}] = ${loopIndex}`);
}
}
currentStep++;
}
// Initialize on page load
window.onload = function() {
initCodeDisplay();
initTestCaseButtons();
updateCaseDescription();
createArrayVisualization();
// Set up event listeners
document.getElementById('nextStep').addEventListener('click', runAnimationStep);
document.getElementById('resetBtn').addEventListener('click', function() {
resetAnimation();
createArrayVisualization();
});
};

365
animations/maps/style.css Normal file
View File

@@ -0,0 +1,365 @@
html, body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
font-size: 16px;
overflow-x: hidden;
}
h1 {
text-align: center;
margin-top: 12px;
margin-bottom: 8px;
font-size: 26px;
}
p {
text-align: center;
margin: 8px 0 12px;
font-size: 15px;
}
.visualization-container {
display: flex;
justify-content: space-between;
max-width: 1800px;
margin: 0 auto;
height: 80vh;
gap: 20px;
padding: 0 15px;
}
.left-panel {
display: flex;
flex-direction: column;
width: 48%;
}
.code-container {
background-color: #f0f0f0;
border-radius: 10px;
padding: 15px;
flex-grow: 1;
overflow-y: auto;
max-height: 52vh;
font-size: 16px;
margin-bottom: 15px;
}
.explanation-result-container {
background-color: #f0f0f0;
border-radius: 10px;
padding: 15px;
display: flex;
flex-direction: column;
}
.color-key {
background-color: #f9f9f9;
border-radius: 5px;
padding: 10px;
margin-top: 10px;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
}
.key-item {
display: flex;
align-items: center;
margin: 4px 12px;
}
.key-color {
width: 18px;
height: 18px;
border-radius: 3px;
margin-right: 8px;
}
.key-current {
background-color: #fffde7;
border: 2px solid #ffeb3b;
}
.key-found {
background-color: #e8f5e9;
border: 2px solid #4caf50;
}
.key-duplicate {
background-color: #ffebee;
border: 2px solid #f44336;
}
.key-description {
font-size: 14px;
}
pre {
margin: 0;
background-color: #f0f0f0;
width: 100%;
}
code {
font-family: Consolas, "Courier New", monospace;
white-space: pre;
display: inline-block;
min-width: 700px;
}
#codeDisplay {
width: 100%;
}
.line-number {
color: #666;
display: inline-block;
min-width: 35px;
font-size: 16px;
}
.code-line {
display: block;
width: 100%;
position: relative;
box-sizing: border-box;
padding: 2px 0;
font-size: 16px;
line-height: 1.5;
}
.code-line.highlighted {
background-color: #ffeb3b;
width: 100%;
display: block;
}
.visualization-panel {
background-color: #f0f0f0;
border-radius: 10px;
padding: 15px;
width: 48%;
height: 75vh;
position: relative;
display: flex;
flex-direction: column;
}
.top-controls {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
}
.test-case-btns {
display: flex;
justify-content: center;
gap: 6px;
flex-wrap: wrap;
flex: 1;
}
.test-btn {
padding: 6px 8px;
font-size: 14px;
border: none;
border-radius: 4px;
background-color: #e0e0e0;
cursor: pointer;
}
.test-btn:hover {
background-color: #d0d0d0;
}
.test-btn.active {
background-color: #2196f3;
color: white;
}
.case-description {
background-color: #e8e8e8;
padding: 10px;
border-radius: 5px;
margin-bottom: 12px;
font-size: 15px;
text-align: center;
line-height: 1.4;
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 12px;
}
#nextStep, #resetBtn {
padding: 8px 15px;
font-size: 16px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
}
#resetBtn {
background-color: #f44336;
}
#nextStep:hover {
background-color: #45a049;
}
#resetBtn:hover {
background-color: #d32f2f;
}
#nextStep:disabled, #resetBtn:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.visualization-area {
display: flex;
flex-direction: column;
gap: 15px;
overflow: hidden;
padding: 15px;
background-color: white;
border-radius: 5px;
margin-bottom: 12px;
max-height: 55vh;
flex-grow: 1;
padding-bottom: 30px;
}
.array-container, .map-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 15px 10px 28px 10px;
background-color: #f9f9f9;
border-radius: 5px;
min-height: 80px;
position: relative;
margin-top: 22px;
}
.array-container::before, .map-container::before {
content: "Array";
position: absolute;
top: -20px;
left: 10px;
font-weight: bold;
font-size: 15px;
}
.map-container::before {
content: "Map";
}
.array-item, .map-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 55px;
height: 55px;
background-color: white;
border: 2px solid #ddd;
border-radius: 5px;
font-weight: bold;
font-size: 16px;
transition: all 0.3s ease;
}
.array-item {
position: relative;
}
.array-item::after {
content: attr(data-index);
position: absolute;
bottom: -20px;
font-size: 13px;
color: #666;
}
.map-item {
width: 90px;
height: 70px;
margin: 4px;
}
.map-key {
font-size: 15px;
margin-bottom: 5px;
}
.map-value {
font-size: 13px;
color: #666;
}
.current {
border-color: #ffeb3b;
background-color: #fffde7;
box-shadow: 0 0 5px #ffeb3b;
}
.found {
border-color: #4caf50;
background-color: #e8f5e9;
box-shadow: 0 0 5px #4caf50;
}
.duplicate {
border-color: #f44336;
background-color: #ffebee;
box-shadow: 0 0 5px #f44336;
}
.explanation {
font-size: 15px;
padding: 12px;
background-color: #e8e8e8;
border-radius: 5px;
min-height: 60px;
line-height: 1.4;
margin-bottom: 12px;
overflow-y: auto;
max-height: 100px;
}
.result-container {
padding: 12px;
background-color: #e8e8e8;
border-radius: 5px;
text-align: center;
font-weight: bold;
font-size: 16px;
}
#result {
color: #2196f3;
font-size: 18px;
}
.success {
color: #4caf50 !important;
}
.failure {
color: #f44336 !important;
}
.color-key-label {
font-weight: bold;
margin-bottom: 5px;
font-size: 15px;
}

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Max heap animation</title>
<style>
body {
position: relative;
}
#codeContainer {
position: absolute;
top: 100px;
right: 50px;
width: 600px;
}
pre {
font-size: 10px;
}
</style>
<script src="konva.js"></script>
<script src="max_heap.js" defer></script>
</head>
<body>
<h1>Max heap animation</h1>
<button id="next">Next Step</button>
<div id="container"></div>
<div id="codeContainer">
<pre id="codeBlock"></pre>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,288 @@
var stage = new Konva.Stage({
container: 'container',
width: 2000,
height: 1000
});
var layer = new Konva.Layer();
stage.add(layer);
var heap = [];
var nodeRadius = 25;
var levelHeight = 80;
var startX = 1100;
var startY = 50;
var horizontalSpacing = 150;
var animationNodes = [];
var animationLines = [];
var currentStep = 0;
var operations = [
{ type: 'push', value: 3 },
{ type: 'push', value: 4 },
{ type: 'push', value: 3 },
{ type: 'push', value: 1 },
{ type: 'push', value: 5 },
{ type: 'pop' },
{ type: 'pop' },
{ type: 'pop' },
{ type: 'pop' },
{ type: 'pop' }
];
var cppCode = [
"#include <iostream>",
"#include <queue>",
"",
"int main() {",
" std::priority_queue<int> maxHeap;",
"",
" maxHeap.push(3);",
" maxHeap.push(4);",
" maxHeap.push(3);",
" maxHeap.push(1);",
" maxHeap.push(5);",
"",
" while (!maxHeap.empty()) {",
" std::cout << maxHeap.top() << \" \";",
" maxHeap.pop();",
" }",
" std::cout << std::endl;",
"",
" return 0;",
"}"
];
var codeBox = new Konva.Rect({
x: 20,
y: 60,
width: 500,
height: 550,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
cornerRadius: 10,
});
layer.add(codeBox);
var consoleBox = new Konva.Rect({
x: 550,
y: 60,
width: 200,
height: 350,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
cornerRadius: 10,
});
layer.add(consoleBox);
var consoleLabel = new Konva.Text({
x: 610,
y: 30,
text: "Console",
fontSize: 24,
fontFamily: 'Calibri',
fill: '#000',
});
layer.add(consoleLabel);
var consoleOutputText = new Konva.Text({
x: 570,
y: 80,
text: "",
fontSize: 20,
fontFamily: 'Calibri',
fill: 'black',
width: 360,
wrap: 'word'
});
layer.add(consoleOutputText);
var consoleContent = "";
function makeCodeLine(x, y, text, id) {
return new Konva.Text({
x: x,
y: y,
text: text,
id: 'line' + id,
fontSize: 18,
fontFamily: 'Calibri',
fill: '#000000',
width: 460,
padding: 5,
});
}
for (let i = 0; i < cppCode.length; i++) {
let t = makeCodeLine(30, 70 + i * 25, cppCode[i], i + 1);
layer.add(t);
}
function makeBold(id) {
for (let i = 1; i <= cppCode.length; i++) {
let line = stage.findOne('#line' + i);
if (line) {
if (i === id) {
line.fontStyle('bold');
} else {
line.fontStyle('normal');
}
}
}
}
function renderCode(step) {
if (step >= 0 && step <= 4) {
makeBold(7 + step);
} else if (step >= 5 && step <= 10) {
makeBold(14);
} else {
makeBold(0);
}
}
function drawNode(x, y, value, color = '#ffffff') {
var group = new Konva.Group({ x: x, y: y });
var circle = new Konva.Circle({
radius: nodeRadius,
fill: color,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
});
var text = new Konva.Text({
text: value.toString(),
fill: 'black',
align: 'center',
width: nodeRadius * 2,
x: -nodeRadius,
y: -7
});
group.add(circle);
group.add(text);
layer.add(group);
animationNodes.push(group);
return group;
}
function makeLine(x1, y1, x2, y2) {
var line = new Konva.Line({
points: [x1, y1, x2, y2],
stroke: '#555',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round'
});
layer.add(line);
animationLines.push(line);
return line;
}
function makeTree() {
animationNodes.forEach(n => n.destroy());
animationLines.forEach(l => l.destroy());
animationNodes = [];
animationLines = [];
if (heap.length === 0) return;
var positions = [];
for (var i = 0; i < heap.length; i++) {
var level = Math.floor(Math.log2(i + 1));
var indexInLevel = i - (2 ** level - 1);
var nodesInLevel = 2 ** level;
var x = startX + (indexInLevel - (nodesInLevel - 1) / 2) * horizontalSpacing * (1.5 - level / 5);
var y = startY + level * levelHeight;
positions.push({ x: x, y: y });
drawNode(x, y, heap[i]);
if (i > 0) {
var parentPos = positions[Math.floor((i - 1) / 2)];
makeLine(parentPos.x, parentPos.y + nodeRadius, x, y - nodeRadius);
}
}
layer.draw();
}
function push(val) {
heap.push(val);
var idx = heap.length - 1;
while (idx > 0) {
var parentIdx = Math.floor((idx - 1) / 2);
if (heap[parentIdx] >= heap[idx]) break;
[heap[parentIdx], heap[idx]] = [heap[idx], heap[parentIdx]];
idx = parentIdx;
}
}
function pop() {
if (heap.length === 0) { return null; }
var max = heap[0];
heap[0] = heap[heap.length - 1];
heap.pop();
var idx = 0;
while (true) {
var left = 2 * idx + 1;
var right = 2 * idx + 2;
var largest = idx;
if (left < heap.length && heap[left] > heap[largest]) largest = left;
if (right < heap.length && heap[right] > heap[largest]) largest = right;
if (largest === idx) break;
[heap[idx], heap[largest]] = [heap[largest], heap[idx]];
idx = largest;
}
return max;
}
function next() {
if (currentStep >= operations.length) {
alert("End of animation! Refresh the page if you want to re-run the animation.");
return;
}
var op = operations[currentStep];
if (op.type === 'push') {
push(op.value);
} else if (op.type === 'pop') {
let val = pop();
if (val !== null) {
consoleContent += val + ' ';
consoleOutputText.text(consoleContent);
}
}
renderCode(currentStep);
makeTree();
currentStep++;
}
document.getElementById('next').addEventListener('click', next);
layer.draw();

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Min heap animation</title>
<style>
body {
position: relative;
}
#codeContainer {
position: absolute;
top: 100px;
right: 50px;
width: 600px;
}
pre {
font-size: 10px;
}
</style>
<script src="konva.js"></script>
<script src="min_heap.js" defer></script>
</head>
<body>
<h1>Min heap animation</h1>
<button id="next">Next Step</button>
<div id="container"></div>
<div id="codeContainer">
<pre id="codeBlock"></pre>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,290 @@
var stage = new Konva.Stage({
container: 'container',
width: 2000,
height: 1000
});
var layer = new Konva.Layer();
stage.add(layer);
var heap = [];
var nodeRadius = 25;
var levelHeight = 80;
var startX = 1100;
var startY = 50;
var horizontalSpacing = 150;
var animationNodes = [];
var animationLines = [];
var currentStep = 0;
var operations = [
{ type: 'push', value: 3 },
{ type: 'push', value: 4 },
{ type: 'push', value: 3 },
{ type: 'push', value: 1 },
{ type: 'push', value: 5 },
{ type: 'pop' },
{ type: 'pop' },
{ type: 'pop' },
{ type: 'pop' },
{ type: 'pop' }
];
var cppCode = [
"#include <iostream>",
"#include <queue>",
"#include <vector>",
"#include <functional>",
"",
"int main() {",
" std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;",
"",
" minHeap.push(3);",
" minHeap.push(4);",
" minHeap.push(3);",
" minHeap.push(1);",
" minHeap.push(5);",
"",
" while (!minHeap.empty()) {",
" std::cout << minHeap.top() << \" \";",
" minHeap.pop();",
" }",
" std::cout << std::endl;",
"",
" return 0;",
"}"
];
var codeBox = new Konva.Rect({
x: 20,
y: 60,
width: 500,
height: 550,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
cornerRadius: 10,
});
layer.add(codeBox);
var consoleBox = new Konva.Rect({
x: 550,
y: 60,
width: 200,
height: 350,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
cornerRadius: 10,
});
layer.add(consoleBox);
var consoleLabel = new Konva.Text({
x: 610,
y: 30,
text: "Console",
fontSize: 24,
fontFamily: 'Calibri',
fill: '#000',
});
layer.add(consoleLabel);
var consoleOutputText = new Konva.Text({
x: 570,
y: 80,
text: "",
fontSize: 20,
fontFamily: 'Calibri',
fill: 'black',
width: 360,
wrap: 'word'
});
layer.add(consoleOutputText);
var consoleContent = "";
function makeCodeLine(x, y, text, id) {
return new Konva.Text({
x: x,
y: y,
text: text,
id: 'line' + id,
fontSize: 18,
fontFamily: 'Calibri',
fill: '#000000',
width: 460,
padding: 5,
});
}
for (let i = 0; i < cppCode.length; i++) {
let t = makeCodeLine(30, 70 + i * 25, cppCode[i], i + 1);
layer.add(t);
}
function makeBold(id) {
for (let i = 1; i <= cppCode.length; i++) {
let line = stage.findOne('#line' + i);
if (line) {
if (i === id) {
line.fontStyle('bold');
} else {
line.fontStyle('normal');
}
}
}
}
function renderCode(step) {
if (step >= 0 && step <= 4) {
makeBold(9 + step);
} else if (step >= 5 && step <= 10) {
makeBold(16);
} else {
makeBold(0);
}
}
function drawNode(x, y, value, color = '#ffffff') {
var group = new Konva.Group({ x: x, y: y });
var circle = new Konva.Circle({
radius: nodeRadius,
fill: color,
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.2,
});
var text = new Konva.Text({
text: value.toString(),
fill: 'black',
align: 'center',
width: nodeRadius * 2,
x: -nodeRadius,
y: -7
});
group.add(circle);
group.add(text);
layer.add(group);
animationNodes.push(group);
return group;
}
function makeLine(x1, y1, x2, y2) {
var line = new Konva.Line({
points: [x1, y1, x2, y2],
stroke: '#555',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round'
});
layer.add(line);
animationLines.push(line);
return line;
}
function makeTree() {
animationNodes.forEach(n => n.destroy());
animationLines.forEach(l => l.destroy());
animationNodes = [];
animationLines = [];
if (heap.length === 0) return;
var positions = [];
for (var i = 0; i < heap.length; i++) {
var level = Math.floor(Math.log2(i + 1));
var indexInLevel = i - (2 ** level - 1);
var nodesInLevel = 2 ** level;
var x = startX + (indexInLevel - (nodesInLevel - 1) / 2) * horizontalSpacing * (1.5 - level / 5);
var y = startY + level * levelHeight;
positions.push({ x: x, y: y });
drawNode(x, y, heap[i]);
if (i > 0) {
var parentPos = positions[Math.floor((i - 1) / 2)];
makeLine(parentPos.x, parentPos.y + nodeRadius, x, y - nodeRadius);
}
}
layer.draw();
}
function push(val) {
heap.push(val);
var idx = heap.length - 1;
while (idx > 0) {
var parentIdx = Math.floor((idx - 1) / 2);
if (heap[parentIdx] <= heap[idx]) break;
[heap[parentIdx], heap[idx]] = [heap[idx], heap[parentIdx]];
idx = parentIdx;
}
}
function pop() {
if (heap.length === 0) { return null; }
var min = heap[0];
heap[0] = heap[heap.length - 1];
heap.pop();
var idx = 0;
while (true) {
var left = 2 * idx + 1;
var right = 2 * idx + 2;
var smallest = idx;
if (left < heap.length && heap[left] < heap[smallest]) smallest = left;
if (right < heap.length && heap[right] < heap[smallest]) smallest = right;
if (smallest === idx) break;
[heap[idx], heap[smallest]] = [heap[smallest], heap[idx]];
idx = smallest;
}
return min;
}
function next() {
if (currentStep >= operations.length) {
alert("End of animation! Refresh the page if you want to re-run the animation.");
return;
}
var op = operations[currentStep];
if (op.type === 'push') {
push(op.value);
} else if (op.type === 'pop') {
let val = pop();
if (val !== null) {
consoleContent += val + ' ';
consoleOutputText.text(consoleContent);
}
}
renderCode(currentStep);
makeTree();
currentStep++;
}
document.getElementById('next').addEventListener('click', next);
layer.draw();

View File

@@ -0,0 +1,55 @@
body {
margin: 0;
background-color: #d3d3d3;
font-family: Arial;
}
#main-container {
display: flex;
width: 90%;
margin: 20px auto;
}
#tree-container {
flex: 3;
height: 600px;
border: 2px solid #333;
background-color: #d3d3d3;
}
#stack-container {
flex: 1;
height: 600px;
border: 2px solid #333;
background-color: #f0f0f0;
margin-left: 10px;
padding: 10px;
box-sizing: border-box;
}
#stack-title {
text-align: center;
font-weight: bold;
margin-bottom: 10px;
}
#controls {
text-align: center;
margin-top: 10px;
}
#log, #final-seq, #status {
width: 90%;
margin: 10px auto;
padding: 10px;
background-color: #fff;
border-radius: 4px;
font-size: 16px;
}
#log {
height: 100px;
overflow-y: auto;
}
button {
padding: 10px 20px;
font-size: 16px;
margin: 0 10px;
}
.label {
font-weight: bold;
}

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>In-Order Traversal (Step-by-Step)</title>
<script src="konva.js"></script>
<link rel="stylesheet" href="inorder.css">
</head>
<body>
<div id="main-container">
<div id="tree-container"></div>
<div id="stack-container">
<div id="stack-title">Stack</div>
<div id="stack-visual"></div>
</div>
</div>
<div id="controls">
<button id="stepBtn">Step</button>
<button id="resetBtn">Start Over</button>
</div>
<div id="status">
<span class="label">Now checking:</span> <span id="currentVal">None</span> |
<span class="label">Status:</span> <span id="reason">Ready</span>
</div>
<div id="log"><strong>Traversal Sequence:</strong> <span id="seq"></span></div>
<div id="final-seq"><strong>Final Sequence:</strong> 4 2 6 5 7 1 3 8 9</div>
<script src="inorder.js"></script>
</body>
</html>

View File

@@ -0,0 +1,266 @@
//set up the main canvas dimensions for tree visualization
//using 75% of 90% window width to leave some margin
const width = window.innerWidth * 0.9 * 0.75;
const height = 600;
//create the main stage for tree visualization
const stage = new Konva.Stage({
container: 'tree-container', //html div id
width: width,
height: height
});
//create and add a layer to the stage for drawing
const layer = new Konva.Layer();
stage.add(layer);
//manually calculated x,y coordinates for visual balance
const positions = {
1: { x: width / 2, y: 80 }, //root node centered
2: { x: width / 2 - 200, y: 180 }, //left child
3: { x: width / 2 + 200, y: 180 }, //right child
4: { x: width / 2 - 300, y: 280 }, //left-left grandchild
5: { x: width / 2 - 100, y: 280 }, //left-right grandchild
6: { x: width / 2 - 150, y: 380 }, //left-right-left great-grandchild
7: { x: width / 2 - 50, y: 380 }, //left-right-right great-grandchild
8: { x: width / 2 + 300, y: 280 }, //right-right grandchild
9: { x: width / 2 + 350, y: 380 } //right-right-right great-grandchild
};
//binary tree structure definition--------------------------------------
const tree = {
val: 1, //root node
left: {
val: 2,
left: { val: 4, left: null, right: null }, //leaf node
right: {
val: 5,
left: { val: 6, left: null, right: null }, //leaf node
right: { val: 7, left: null, right: null } //leaf node
}
},
right: {
val: 3,
left: null, //no left child
right: {
val: 8,
left: null, //no left child
right: { val: 9, left: null, right: null } //leaf node
}
}
};
//store references to all visual node elements
const nodes = {};
/**
* recursively draws the tree starting from given node
*/
function drawTree(node, parent = null) {
if (!node) return; //base case
//get pre-defined position for this node
const pos = positions[node.val];
//if not root node, draw line to parent
if (parent) {
const parentPos = positions[parent.val];
layer.add(new Konva.Line({
points: [parentPos.x, parentPos.y, pos.x, pos.y],
stroke: 'black',
strokeWidth: 2
}));
}
//create circle for node
const circle = new Konva.Circle({
x: pos.x,
y: pos.y,
radius: 20,
fill: 'white',
stroke: 'black',
strokeWidth: 2
});
//create text label for node value
const text = new Konva.Text({
x: pos.x - 5, //adjust slightly for centering
y: pos.y - 8,
text: node.val.toString(),
fontSize: 16,
fill: 'black'
});
//add elements to layer
layer.add(circle);
layer.add(text);
//store references for later manipulation
nodes[node.val] = { circle, text };
//recursively draw left and right children
drawTree(node.left, node);
drawTree(node.right, node);
}
//create a separate stage for stack visualization
const stackStage = new Konva.Stage({
container: 'stack-visual', //html div id
width: 200, //narrower since it's just for stack
height: 500 //tall enough to show several stack items
});
//create and add layer for stack visualization
const stackLayer = new Konva.Layer();
stackStage.add(stackLayer);
//visual elements for stack representation
let stackVisual = [];
/**
* visualizes pushing a value onto the stack
*/
function pushStack(val) {
//create rectangle for stack element
const box = new Konva.Rect({
x: 50,
y: 400 - stackVisual.length * 30, //stack grows upward
width: 40,
height: 25,
fill: '#89CFF0', //light blue color
stroke: 'black'
});
//create text label for stack element
const label = new Konva.Text({
x: 60,
y: 405 - stackVisual.length * 30,
text: val.toString(),
fontSize: 16,
fill: 'black'
});
//add to stack layer
stackLayer.add(box);
stackLayer.add(label);
stackVisual.push({ box, label }); //store reference
stackLayer.draw(); //redraw stack layer
}
/**
* visualizes popping from the stack
*/
function popStack() {
const item = stackVisual.pop();
if (item) {
//remove visual elements
item.box.destroy();
item.label.destroy();
stackLayer.draw();
}
}
/**
* highlights a node with specified color
*/
function highlight(val, color = 'yellow') {
const node = nodes[val];
if (node) {
node.circle.fill(color);
layer.draw(); //redraw to show changes
}
}
/**
* resets all node highlights to white
*/
function resetHighlights() {
for (let val in nodes) {
nodes[val].circle.fill('white');
}
layer.draw();
}
/**
* updates the status display
*/
function updateStatus(current, reason) {
document.getElementById('currentVal').innerText = current ?? 'None';
document.getElementById('reason').innerText = reason;
}
/**
* logs a visited node to the sequence display
*/
function logVisit(val) {
const seq = document.getElementById('seq');
seq.innerText += ` ${val}`;
}
/**
* resets the entire visualization to initial state
*/
function resetAll() {
//clear stack visualization
while (stackVisual.length > 0) {
popStack();
}
//reset traversal state
stack.length = 0;
current = tree;
stepReady = true;
//clear displays
document.getElementById('seq').innerText = '';
updateStatus('None', 'Ready');
resetHighlights();
}
//traversal state variables
let current = tree; //current node being processed
const stack = []; //stack used for traversal
let stepReady = true; //flag to control step-by-step execution
//handler for step button - performs one step of inorder traversal
document.getElementById("stepBtn").onclick = () => {
if (!stepReady) return; //don't allow overlapping steps
stepReady = false;
if (current) {
//step 1: go as far left as possible
updateStatus(current.val, 'go left');
stack.push(current);
pushStack(current.val);
current = current.left;
stepReady = true;
return;
}
if (stack.length > 0) {
//step 2: visit node (inorder)
current = stack.pop();
updateStatus(current.val, 'visit node');
popStack();
highlight(current.val);
logVisit(current.val);
//step 3: move to right subtree
setTimeout(() => {
highlight(current.val, 'white');
current = current.right;
stepReady = true;
}, 500);
} else {
//traversal complete
updateStatus('None', 'done!');
}
};
//event handler for reset button
document.getElementById("resetBtn").onclick = resetAll;
//initial render of the tree
drawTree(tree);
layer.draw();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
body {
margin: 0;
background-color: #d3d3d3;
font-family: Arial;
}
#main-container {
display: flex;
width: 90%;
margin: 20px auto;
}
#tree-container {
flex: 3;
height: 600px;
border: 2px solid #333;
background-color: #d3d3d3;
}
#stack-container {
flex: 1;
height: 600px;
border: 2px solid #333;
background-color: #f0f0f0;
margin-left: 10px;
padding: 10px;
box-sizing: border-box;
}
#stack-title {
text-align: center;
font-weight: bold;
margin-bottom: 10px;
}
#controls {
text-align: center;
margin-top: 10px;
}
#log, #final-seq, #status {
width: 90%;
margin: 10px auto;
padding: 10px;
background-color: #fff;
border-radius: 4px;
font-size: 16px;
}
#log {
height: 100px;
overflow-y: auto;
}
button {
padding: 10px 20px;
font-size: 16px;
margin: 0 10px;
}
.label {
font-weight: bold;
}

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Post-Order Traversal (Step-by-Step)</title>
<script src="konva.js"></script>
<link rel="stylesheet" href="postorder.css">
</head>
<body>
<div id="main-container">
<div id="tree-container"></div>
<div id="stack-container">
<div id="stack-title">Stack</div>
<div id="stack-visual"></div>
</div>
</div>
<div id="controls">
<button id="stepBtn">Step</button>
<button id="resetBtn">Start Over</button>
</div>
<div id="status">
<span class="label">Now checking:</span> <span id="currentVal">None</span> |
<span class="label">Status:</span> <span id="reason">Ready</span>
</div>
<div id="log"><strong>Traversal Sequence:</strong> <span id="seq"></span></div>
<div id="final-seq"><strong>Final Sequence:</strong> 4 6 7 5 2 9 8 3 1</div>
<script src="postorder.js"></script>
</body>
</html>

View File

@@ -0,0 +1,283 @@
//set up the main canvas dimensions for tree visualization
//using 75% of 90% window width to leave some margin
const width = window.innerWidth * 0.9 * 0.75;
const height = 600;
//create the main stage for tree visualization
const stage = new Konva.Stage({
container: 'tree-container', //HTML div id
width: width,
height: height
});
//create and add a layer to the stage for drawing
let layer = new Konva.Layer();
stage.add(layer);
//create a separate stage for stack visualization
const stackStage = new Konva.Stage({
container: 'stack-visual', //HTML div id
width: 200, //narrower since it's just for stack
height: 500 //tall enough to show several stack items
});
//create and add layer for stack visualization
let stackLayer = new Konva.Layer();
stackStage.add(stackLayer);
//manually calculated x,y coordinates for visual balance
const positions = {
1: { x: width / 2, y: 80 }, //root node centered
2: { x: width / 2 - 200, y: 180 }, //left child
3: { x: width / 2 + 200, y: 180 }, //right child
4: { x: width / 2 - 300, y: 280 }, //left-left grandchild
5: { x: width / 2 - 100, y: 280 }, //left-right grandchild
6: { x: width / 2 - 150, y: 380 }, //left-right-left great-grandchild
7: { x: width / 2 - 50, y: 380 }, //left-right-right great-grandchild
8: { x: width / 2 + 300, y: 280 }, //right-right grandchild
9: { x: width / 2 + 350, y: 380 } //right-right-right great-grandchild
};
let nodes = {};//store references to all visual node elements
let stackVisual = []; //visual elements for stack rep
let stack = []; //stack used for traversal
let current = null;// Current node being processe
let lastVisited = null; //track last visited node for postorder logic
let stepReady = true; //flag to control step-by-step execution
//binary tree structure definition--------------------------------------
const treeData = {
val: 1,//root node
left: {
val: 2,
left: { val: 4, left: null, right: null },//leaf node
right: {
val: 5,
left: { val: 6, left: null, right: null },//leaf node
right: { val: 7, left: null, right: null }//leaf node
}
},
right: {
val: 3,
left: null,//no left child
right: {
val: 8,
left: null,//no left child
right: { val: 9, left: null, right: null }//leaf node
}
}
};
/**
* recursively draws the tree starting from given node
*/
function drawTree(node, parent = null) {
if (!node) return; // Base case
//get pre-defined position for this node
const pos = positions[node.val];
//if not root node, draw line to parent
if (parent) {
const parentPos = positions[parent.val];
layer.add(new Konva.Line({
points: [parentPos.x, parentPos.y, pos.x, pos.y],
stroke: 'black',
strokeWidth: 2
}));
}
//create circle for node
const circle = new Konva.Circle({
x: pos.x,
y: pos.y,
radius: 20,
fill: 'white',
stroke: 'black',
strokeWidth: 2
});
//create text label for node value
const text = new Konva.Text({
x: pos.x - 5, //adjust slightly for centering
y: pos.y - 8,
text: node.val.toString(),
fontSize: 16,
fill: 'black'
});
//add elements to layer
layer.add(circle);
layer.add(text);
//store references for later manipulation
nodes[node.val] = { circle, text };
//recursively draw left and right children
drawTree(node.left, node);
drawTree(node.right, node);
}
/**
* visualizes pushing a value onto the stack
*/
function pushStack(val) {
//create rectangle for stack element
const box = new Konva.Rect({
x: 50,
y: 400 - stackVisual.length * 30, //stack grows upward
width: 40,
height: 25,
fill: '#89CFF0', //light blue color
stroke: 'black'
});
//create text label for stack element
const label = new Konva.Text({
x: 60,
y: 405 - stackVisual.length * 30,
text: val.toString(),
fontSize: 16,
fill: 'black'
});
// Add to stack layer
stackLayer.add(box);
stackLayer.add(label);
stackVisual.push({ box, label });//store reference
stackLayer.draw();//redraw stack layer
}
/**
* visualizes popping from the stack
*/
function popStack() {
const item = stackVisual.pop();
if (item) {
//remove visual elements
item.box.destroy();
item.label.destroy();
stackLayer.draw();
}
}
/**
* highlights a node with specified color
*/
function highlight(val, color = 'yellow') {
const node = nodes[val];
if (node) {
node.circle.fill(color);
layer.draw(); //redraw to show changes
}
}
/**
* resets all node highlights to white
*/
function resetHighlights() {
for (let val in nodes) {
nodes[val].circle.fill('white');
}
layer.draw();
}
/**
* updates the status display
*/
function updateStatus(current, reason) {
document.getElementById('currentVal').innerText = current ?? 'None';
document.getElementById('reason').innerText = reason;
}
/**
* logs a visited node to the sequence display
*/
function logVisit(val) {
const seq = document.getElementById('seq');
seq.innerText += ` ${val}`;
}
/**
* resets the entire visualization to initial state
*/
function resetAll() {
//clear existing layers
layer.destroy();
stackLayer.destroy();
//create new layers
layer = new Konva.Layer();
stackLayer = new Konva.Layer();
//add layers to stages
stage.add(layer);
stackStage.add(stackLayer);
//reset all variables
nodes = {};
stackVisual = [];
stack = [];
current = treeData;//start at root
lastVisited = null;
stepReady = true;
//clear displays
document.getElementById('seq').innerText = '';
updateStatus('None', 'Ready');
//redraw tree
drawTree(treeData);
layer.draw();
}
//handler for step button - performs one step of postorder traversal
document.getElementById("stepBtn").onclick = () => {
if (!stepReady) return; //don't allow overlapping steps
stepReady = false;
if (current) {
//step 1: Go as far left as possible
updateStatus(current.val, 'Going left');
stack.push(current);
pushStack(current.val);
current = current.left;
stepReady = true;
return;
}
if (stack.length > 0) {
const peekNode = stack[stack.length - 1];//look at top of stack
if (peekNode.right && lastVisited !== peekNode.right) {
//step 2: If right exists and not visited, go right
updateStatus(peekNode.val, 'Going right');
current = peekNode.right; //move to right child
stepReady = true;
} else {
//step 3: Visit the node (left and right done)
const node = stack.pop();
popStack(); //remove from visual stack
updateStatus(node.val, 'Visiting node');
highlight(node.val); //visually mark node
logVisit(node.val); //add to sequence
lastVisited = node; //remember we visited this
//reset highlight after delay
setTimeout(() => {
highlight(node.val, 'white');
stepReady = true;
}, 500);
}
} else {
//traversal complete
updateStatus('None', 'Done!');
}
};
//event handler for reset button
document.getElementById("resetBtn").onclick = resetAll;
//initialize visualization
resetAll();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
body {
margin: 0;
background-color: #d3d3d3;
font-family: Arial;
}
#main-container {
display: flex;
width: 90%;
margin: 20px auto;
}
#tree-container {
flex: 3;
height: 600px;
border: 2px solid #333;
background-color: #d3d3d3;
}
#stack-container {
flex: 1;
height: 600px;
border: 2px solid #333;
background-color: #f0f0f0;
margin-left: 10px;
padding: 10px;
box-sizing: border-box;
}
#stack-title {
text-align: center;
font-weight: bold;
margin-bottom: 10px;
}
#controls {
text-align: center;
margin-top: 10px;
}
#log, #final-seq, #status {
width: 90%;
margin: 10px auto;
padding: 10px;
background-color: #fff;
border-radius: 4px;
font-size: 16px;
}
#log {
height: 100px;
overflow-y: auto;
}
button {
padding: 10px 20px;
font-size: 16px;
margin: 0 10px;
}
.label {
font-weight: bold;
}

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pre-Order Traversal (Step-by-Step)</title>
<script src="konva.js"></script>
<link rel="stylesheet" href="preorder.css">
</head>
<body>
<div id="main-container">
<div id="tree-container"></div>
<div id="stack-container">
<div id="stack-title">Stack</div>
<div id="stack-visual"></div>
</div>
</div>
<div id="controls">
<button id="stepBtn">Step</button>
<button id="resetBtn">Start Over</button>
</div>
<div id="status">
<span class="label">Now checking:</span> <span id="currentVal">None</span> |
<span class="label">Status:</span> <span id="reason">Ready</span>
</div>
<div id="log"><strong>Traversal Sequence:</strong> <span id="seq"></span></div>
<div id="final-seq"><strong>Final Sequence:</strong> 1 2 4 5 6 7 3 8 9</div>
<script src="preorder.js"></script>
</body>
</html>

View File

@@ -0,0 +1,278 @@
//set up the main canvas dimensions for tree visualization
//using 75% of 90% window width to leave some margin
const width = window.innerWidth * 0.9 * 0.75;
const height = 600;
//create the main stage for tree visualization
const stage = new Konva.Stage({
container: 'tree-container', //html div id
width: width,
height: height
});
//create and add a layer to the stage for drawing
let layer = new Konva.Layer();
stage.add(layer);
//create a separate stage for stack visualization
const stackStage = new Konva.Stage({
container: 'stack-visual', //html div id
width: 200, //narrower since it's just for stack
height: 500 //tall enough to show several stack items
});
//create and add layer for stack visualization
let stackLayer = new Konva.Layer();
stackStage.add(stackLayer);
//manually calculated x,y coordinates for visual balance
const positions = {
1: { x: width / 2, y: 80 }, //root node centered
2: { x: width / 2 - 200, y: 180 }, //left child
3: { x: width / 2 + 200, y: 180 }, //right child
4: { x: width / 2 - 300, y: 280 }, //left-left grandchild
5: { x: width / 2 - 100, y: 280 }, //left-right grandchild
6: { x: width / 2 - 150, y: 380 }, //left-right-left great-grandchild
7: { x: width / 2 - 50, y: 380 }, //left-right-right great-grandchild
8: { x: width / 2 + 300, y: 280 }, //right-right grandchild
9: { x: width / 2 + 350, y: 380 } //right-right-right great-grandchild
};
let nodes = {}; //store references to all visual node elements
let stackVisual = []; //visual elements for stack representation
let stack = []; //stack used for traversal
let current = null; //current node being processed
let stepReady = true; //flag to control step-by-step execution
//binary tree structure definition--------------------------------------
const treeData = {
val: 1, //root node
left: {
val: 2,
left: { val: 4, left: null, right: null }, //leaf node
right: {
val: 5,
left: { val: 6, left: null, right: null }, //leaf node
right: { val: 7, left: null, right: null } //leaf node
}
},
right: {
val: 3,
left: null, //no left child
right: {
val: 8,
left: null, //no left child
right: { val: 9, left: null, right: null } //leaf node
}
}
};
/**
* recursively draws the tree starting from given node
*/
function drawTree(node, parent = null) {
if (!node) return; //base case
//get pre-defined position for this node
const pos = positions[node.val];
//if not root node, draw line to parent
if (parent) {
const parentPos = positions[parent.val];
layer.add(new Konva.Line({
points: [parentPos.x, parentPos.y, pos.x, pos.y],
stroke: 'black',
strokeWidth: 2
}));
}
//create circle for node
const circle = new Konva.Circle({
x: pos.x,
y: pos.y,
radius: 20,
fill: 'white',
stroke: 'black',
strokeWidth: 2
});
//create text label for node value
const text = new Konva.Text({
x: pos.x - 5, //adjust slightly for centering
y: pos.y - 8,
text: node.val.toString(),
fontSize: 16,
fill: 'black'
});
//add elements to layer
layer.add(circle);
layer.add(text);
//store references for later manipulation
nodes[node.val] = { circle, text };
//recursively draw left and right children
drawTree(node.left, node);
drawTree(node.right, node);
}
/**
* visualizes pushing a value onto the stack
*/
function pushStack(val) {
//create rectangle for stack element
const box = new Konva.Rect({
x: 50,
y: 400 - stackVisual.length * 30, //stack grows upward
width: 40,
height: 25,
fill: '#89CFF0', //light blue color
stroke: 'black'
});
//create text label for stack element
const label = new Konva.Text({
x: 60,
y: 405 - stackVisual.length * 30,
text: val.toString(),
fontSize: 16,
fill: 'black'
});
//add to stack layer
stackLayer.add(box);
stackLayer.add(label);
stackVisual.push({ box, label });//store reference
stackLayer.draw(); //redraw stack layer
}
/**
* visualizes popping from the stack
*/
function popStack() {
const item = stackVisual.pop();
if (item) {
//remove visual elements
item.box.destroy();
item.label.destroy();
stackLayer.draw();
}
}
/**
* highlights a node with specified color
*/
function highlight(val, color = 'yellow') {
const node = nodes[val];
if (node) {
node.circle.fill(color);
layer.draw();//redraw to show changes
}
}
/**
* resets all node highlights to white
*/
function resetHighlights() {
for (let val in nodes) {
nodes[val].circle.fill('white');
}
layer.draw();
}
/**
* updates the status display
*/
function updateStatus(current, reason) {
document.getElementById('currentVal').innerText = current ?? 'None';
document.getElementById('reason').innerText = reason;
}
/**
* logs a visited node to the sequence display
*/
function logVisit(val) {
const seq = document.getElementById('seq');
seq.innerText += ` ${val}`;
}
/**
* resets the entire visualization to initial state
*/
function resetAll() {
//clear existing layers
layer.destroy();
stackLayer.destroy();
//create new layers
layer = new Konva.Layer();
stackLayer = new Konva.Layer();
//add layers to stages
stage.add(layer);
stackStage.add(stackLayer);
//reset all variables
nodes = {};
stackVisual = [];
stack = [];
current = treeData; //start at root
stepReady = true;
//clear displays
document.getElementById('seq').innerText = '';
updateStatus('None', 'Ready');
//redraw tree
drawTree(treeData);
layer.draw();
}
//handler for step button - performs one step of preorder traversal
document.getElementById("stepBtn").onclick = () => {
if (!stepReady) return;//don't allow overlapping steps
stepReady = false;
if (current) {
//step 1: visit node first (preorder)
updateStatus(current.val, 'visiting node');
highlight(current.val);
logVisit(current.val);
//push right child first so left is processed first
if (current.right) {
stack.push(current.right);
pushStack(current.right.val);
}
//move to left child
const prevcurrent = current.val;
current = current.left;
//reset highlight after delay
setTimeout(() => {
highlight(prevcurrent, 'white');
stepReady = true;
}, 500);
return;
}
if (stack.length > 0) {
//step 2: process nodes from stack
current = stack.pop();
popStack();
updateStatus(current.val, 'popped from stack');
stepReady = true;
} else {
//traversal complete
updateStatus('None', 'done!');
}
};
//event handler for reset button
document.getElementById("resetBtn").onclick = resetAll;
//initialize visualization
resetAll();

View File

@@ -0,0 +1,146 @@
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500&display=swap');
/* Base styling */
body {
margin: 0;
background-color: #f7f8fa;
font-family: 'Roboto', sans-serif;
color: #333;
}
/* Layout containers */
#main-container {
display: flex;
width: 90%;
max-width: 1200px;
margin: 30px auto;
gap: 20px;
}
#tree-container,
#stack-container {
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
padding: 16px;
}
#tree-container {
flex: 3;
height: 600px;
}
#stack-container {
flex: 1;
height: 600px;
box-sizing: border-box;
}
/* Stack title */
#stack-title {
font-size: 20px;
text-align: center;
margin-bottom: 12px;
color: #555;
}
/* Controls */
#tabs {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 20px;
}
.tab-btn {
background: transparent;
color: #555;
padding: 8px 16px;
font-size: 16px;
border: none;
border-bottom: 3px solid transparent;
cursor: pointer;
transition: color 0.2s, border-color 0.2s;
}
.tab-btn:hover {
color: #007bff;
}
.tab-btn.active,
.tab-btn[style*="font-weight: bold"] {
color: #007bff;
border-color: #007bff;
}
#controls {
display: flex;
justify-content: center;
gap: 16px;
margin: 20px 0;
}
button {
padding: 12px 24px;
font-size: 16px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s ease, transform 0.1s ease;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
button:hover {
background-color: #006ae6;
}
button:active {
transform: translateY(1px);
}
/* Status & logs */
#status,
#log,
#final-seq {
width: 90%;
max-width: 1200px;
margin: 10px auto;
padding: 16px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
#status {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 18px;
}
#log {
height: 120px;
overflow-y: auto;
}
.label {
font-weight: 500;
color: #666;
}
#currentVal {
color: #007bff;
font-weight: 500;
}
#reason {
color: #ff5722;
font-weight: 500;
}
#final-seq {
font-weight: 500;
color: #28a745;
}

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<!-- Author: Felicity Hill -->
<html>
<head>
<meta charset="utf-8">
<title>Iterative Traversal (Step-by-Step)</title>
<script src="../../konva.js"></script>
<link rel="stylesheet" href="traversal.css">
</head>
<body>
<div id="main-container">
<div id="tree-container"></div>
<div id="stack-container">
<div id="stack-title">Stack</div>
<div id="stack-visual"></div>
</div>
</div>
<div id="controls">
<button id="stepBtn">Step</button>
<button id="resetBtn">Start Over</button>
</div>
<div id="status">
<span class="label">Now checking:</span> <span id="currentVal">None</span> |
<span class="label">Status:</span> <span id="reason">Ready</span>
</div>
<div id="log"><strong>Traversal Sequence:</strong> <span id="seq"></span></div>
<div id="final-seq"><strong>Final Sequence:</strong> 4 2 6 5 7 1 3 8 9</div>
<script src="traversal.js"></script>
</body>
</html>

View File

@@ -0,0 +1,251 @@
// Canvas dimensions
const width = window.innerWidth * 0.9 * 0.75;
const height = 600;
// Create Konva stage & layer
const stage = new Konva.Stage({
container: 'tree-container',
width: width,
height: height
});
const layer = new Konva.Layer();
stage.add(layer);
// Node positions (9 as left child of 8)
const positions = {
1: { x: width/2, y: 80 },
2: { x: width/2 -200, y:180 },
3: { x: width/2 +200, y:180 },
4: { x: width/2 -300, y:280 },
5: { x: width/2 -100, y:280 },
6: { x: width/2 -150, y:380 },
7: { x: width/2 -50, y:380 },
8: { x: width/2 +300, y:280 },
9: { x: width/2 +250, y:380 }
};
// Tree data
const tree = {
val: 1,
left: {
val: 2,
left: { val: 4, left: null, right: null },
right: {
val: 5,
left: { val: 6, left: null, right: null },
right: { val: 7, left: null, right: null }
}
},
right: {
val: 3,
left: null,
right: {
val: 8,
left: { val: 9, left: null, right: null },
right: null
}
}
};
// Store node visuals
const nodes = {};
// Draw a single node
function drawNode(x, y, label) {
const circle = new Konva.Circle({ x, y, radius: 20, fill: 'white', stroke: '#333', strokeWidth: 2 });
const text = new Konva.Text({ x: x - 5, y: y - 8, text: label.toString(), fontSize: 16, fill: '#333' });
layer.add(circle, text);
nodes[label] = { circle, text };
}
// Draw edge between parent and child
function drawEdge(parentVal, childVal) {
const p = positions[parentVal], c = positions[childVal];
const dx = c.x - p.x, dy = c.y - p.y;
const d = Math.hypot(dx, dy), ox = (dx/d) * 20, oy = (dy/d) * 20;
const line = new Konva.Line({ points:[p.x+ox,p.y+oy, c.x-ox,c.y-oy], stroke: '#333', strokeWidth: 2 });
layer.add(line);
}
// Make tree
function renderTree(node, parent = null) {
if (!node) return;
drawNode(positions[node.val].x, positions[node.val].y, node.val);
if (parent) drawEdge(parent.val, node.val);
renderTree(node.left, node);
renderTree(node.right, node);
}
renderTree(tree);
// Stack visualization
const stackStage = new Konva.Stage({ container: 'stack-visual', width: 200, height: 500 });
const stackLayer = new Konva.Layer();
stackStage.add(stackLayer);
let stackVisual = [];
function pushStack(val) {
const y = 400 - stackVisual.length * 30;
const box = new Konva.Rect({ x:50, y, width:40, height:25, fill:'#007bff', stroke:'#0056b3', cornerRadius:4 });
const txt = new Konva.Text({ x:58, y: y+4, text: val.toString(), fontSize:14, fill:'#fff' });
stackLayer.add(box, txt);
stackVisual.push({ box, txt });
stackLayer.draw();
}
function popStack() {
const it = stackVisual.pop();
if (it) { it.box.destroy(); it.txt.destroy(); stackLayer.draw(); }
}
// Highlight current node
function highlight(val) {
for (const v in nodes) nodes[v].circle.fill('white');
if (val != null && nodes[val]) nodes[val].circle.fill('#ffc107');
layer.draw();
}
function updateStatus(current, reason) {
document.getElementById('currentVal').innerText = current != null ? current : 'None';
const reasonEl = document.getElementById('reason');
reasonEl.innerText = reason;
reasonEl.style.whiteSpace = 'normal';
reasonEl.style.display = 'inline-block';
reasonEl.style.maxWidth = '70%';
reasonEl.style.overflowWrap = 'break-word';
}
function logVisit(val) { document.getElementById('seq').innerText += ` ${val}`; }
// Traversal state
let current = tree;
let lastVisited = null;
const stack = [];
let stepReady = true;
// Final sequences for display
const finalSeqs = {
'In-Order': '4 2 6 5 7 1 3 9 8',
'Pre-Order': '1 2 4 5 6 7 3 8 9',
'Post-Order': '4 6 7 5 2 9 8 3 1'
};
// Tabs
const modes = ['In-Order', 'Pre-Order', 'Post-Order'];
let currentMode = 'In-Order';
const tabsDiv = document.createElement('div');
tabsDiv.id = 'tabs'; tabsDiv.style.textAlign = 'center'; tabsDiv.style.margin = '10px';
modes.forEach(mode => {
const btn = document.createElement('button');
btn.innerText = mode; btn.className = 'tab-btn';
btn.onclick = () => {
currentMode = mode;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Update final sequence display
finalSeqEl.innerHTML = `<strong>Final Sequence (${mode}):</strong> ${finalSeqs[mode]}`;
resetAll();
};
if (mode === currentMode) btn.classList.add('active');
tabsDiv.appendChild(btn);
});
document.body.insertBefore(tabsDiv, document.getElementById('controls'));
//Initialize final sequence display
const finalSeqEl = document.getElementById('final-seq');
finalSeqEl.innerHTML = `<strong>Final Sequence (${currentMode}):</strong> ${finalSeqs[currentMode]}`;
// Step functions
function stepInOrder() {
if (current) {
highlight(current.val);
updateStatus(current.val, `We have reached node ${current.val}. Push it onto the stack to remember it for later, then move to its left child to continue traversing the left subtree.`);
stack.push(current); pushStack(current.val);
current = current.left;
stepReady = true;
return;
}
if (stack.length) {
current = stack.pop(); popStack();
highlight(current.val);
updateStatus(current.val, `Left subtree complete. Now visit node ${current.val} and output it in the in-order sequence, then move to its right child to explore its right subtree.`);
logVisit(current.val);
setTimeout(() => { current = current.right; stepReady = true; }, 500);
} else {
highlight(null);
updateStatus(null, 'In-order traversal complete! All nodes have been visited.');
}
}
function stepPreOrder() {
if (current) {
highlight(current.val);
updateStatus(current.val, `Output node ${current.val} as the next in pre-order sequence, then push it onto the stack so we can revisit its right subtree later, and move to its left child.`);
logVisit(current.val);
stack.push(current); pushStack(current.val);
current = current.left;
stepReady = true;
return;
}
if (stack.length) {
current = stack.pop(); popStack();
highlight(current.val);
updateStatus(current.val, `Finished left subtree of node ${current.val}. Now proceed to explore its right subtree.`);
current = current.right;
} else {
highlight(null);
updateStatus(null, 'Pre-order traversal complete! All nodes have been visited.');
}
stepReady = true;
}
function stepPostOrder() {
if (current) {
highlight(current.val);
updateStatus(current.val, `Push node ${current.val} onto the stack and move to its left child. In post-order, we process children before the node itself.`);
stack.push(current); pushStack(current.val);
current = current.left;
stepReady = true;
return;
}
if (stack.length) {
const peek = stack[stack.length - 1];
if (peek.right && lastVisited !== peek.right) {
updateStatus(peek.val, `Left subtree of node ${peek.val} done. Now move to its right subtree to continue processing children first.`);
current = peek.right;
stepReady = true;
} else {
current = stack.pop(); popStack();
highlight(current.val);
updateStatus(current.val, `Both children of node ${current.val} have been visited. Now output node ${current.val} for post-order sequence.`);
logVisit(current.val);
lastVisited = current;
current = null;
stepReady = true;
}
} else {
highlight(null);
updateStatus(null, 'Post-order traversal complete! All nodes have been visited.');
}
}
// Handles step function
document.getElementById('stepBtn').onclick = () => {
if (!stepReady) return;
stepReady = false;
if (currentMode === 'In-Order') stepInOrder();
else if (currentMode === 'Pre-Order') stepPreOrder();
else if (currentMode === 'Post-Order') stepPostOrder();
};
// Clear/Reset
document.getElementById('resetBtn').onclick = resetAll;
function resetAll() {
while (stackVisual.length) popStack();
stack.length = 0;
current = tree;
lastVisited = null;
stepReady = true;
document.getElementById('seq').innerText = '';
updateStatus(null, 'Ready to begin traversal. Click "Step" to start.');
highlight(null);
}
layer.draw();

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<!-- Author: Evan Lee -->
<html>
<head>
<meta charset="UTF-8">
<title>Morris Post Order Traversal Animation & Code Display</title>
<link rel="stylesheet" href="style.css">
<!-- Load Konva.js -->
<script src="../../konva.js"></script>
<script src="script.js" defer></script>
</head>
<body>
<h1>Morris Post-Order Traversal Visualization</h1>
<p>This animation shows how the Morris post-order traversal algorithm works without using a stack or recursion. Click the "Next Step" button to run the animation step by step.</p>
<div class="visualization-container">
<div class="code-container">
<pre><code id="codeDisplay"></code></pre>
</div>
<div class="tree-container">
<div class="color-key">
<div class="color-item">
<div class="color-box" style="background-color: #ffeb3b;"></div>
<span>Current Node</span>
</div>
<div class="color-item">
<div class="color-box" style="background-color: #2196f3;"></div>
<span>Rightmost Node</span>
</div>
<div class="color-item">
<div class="color-box" style="background-color: #4caf50;"></div>
<span>Visited Node</span>
</div>
<div class="color-item">
<div class="color-box" style="background-color: #03a9f4;"></div>
<span>Thread Created</span>
</div>
<div class="color-item">
<div class="color-arrow" style="background-color: #888;"></div>
<span>Tree Edge</span>
</div>
<div class="color-item">
<div class="dotted-line"></div>
<span>Thread Edge</span>
</div>
<div class="color-item">
<div class="color-arrow" style="background-color: #e91e63;"></div>
<span>Reversed Edge</span>
</div>
</div>
<button id="nextStep">Next Step</button>
<div id="container"></div>
<div class="explanation" id="treeExplanation">
Post-order traversal output: <span id="output"></span>
</div>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,623 @@
// Define code content
const codeContent = [
'<span class="line-number">0.</span> <span style="color:blue">void</span> postorderTraversal(<span style="color:blue">TreeNode</span>* root) {',
'<span class="line-number">1.</span> <span style="color:blue">TreeNode</span>* current = root;',
'<span class="line-number">2.</span> <span style="color:blue">TreeNode</span>* rightmost;',
'<span class="line-number">3.</span> <span style="color:blue">while</span> (current != <span style="color:blue">nullptr</span>) {',
'<span class="line-number">4.</span> <span style="color:blue">if</span> (current->left != <span style="color:blue">nullptr</span>) {',
'<span class="line-number">5.</span> rightmost = current->left;',
'<span class="line-number">6.</span> <span style="color:blue">while</span> (rightmost->right != <span style="color:blue">nullptr</span> && rightmost->right != current) {',
'<span class="line-number">7.</span> rightmost = rightmost->right;',
'<span class="line-number">8.</span> }',
'<span class="line-number">9.</span> <span style="color:blue">if</span> (rightmost->right == <span style="color:blue">nullptr</span>) {',
'<span class="line-number">10.</span> rightmost->right = current;',
'<span class="line-number">11.</span> current = current->left;',
'<span class="line-number">12.</span> } <span style="color:blue">else</span> {',
'<span class="line-number">13.</span> rightmost->right = <span style="color:blue">nullptr</span>;',
'<span class="line-number">14.</span> reverseTraverseRightEdge(current->left);',
'<span class="line-number">15.</span> current = current->right;',
'<span class="line-number">16.</span> }',
'<span class="line-number">17.</span> } <span style="color:blue">else</span> {',
'<span class="line-number">18.</span> current = current->right;',
'<span class="line-number">19.</span> }',
'<span class="line-number">20.</span> }',
'<span class="line-number">21.</span> reverseTraverseRightEdge(root); <span style="color:green">// final right edge</span>',
'<span class="line-number">22.</span> <span style="color:blue">return</span>;',
'<span class="line-number">23.</span> }',
'<span class="line-number">24.</span> ',
'<span class="line-number">25.</span> <span style="color:blue">TreeNode</span>* reverse(<span style="color:blue">TreeNode</span>* head) {',
'<span class="line-number">26.</span> <span style="color:blue">TreeNode</span>* prev = <span style="color:blue">nullptr</span>;',
'<span class="line-number">27.</span> <span style="color:blue">TreeNode</span>* next = <span style="color:blue">nullptr</span>;',
'<span class="line-number">28.</span> <span style="color:blue">while</span> (head != <span style="color:blue">nullptr</span>) {',
'<span class="line-number">29.</span> next = head->right;',
'<span class="line-number">30.</span> head->right = prev;',
'<span class="line-number">31.</span> prev = head;',
'<span class="line-number">32.</span> head = next;',
'<span class="line-number">33.</span> }',
'<span class="line-number">34.</span> <span style="color:blue">return</span> prev;',
'<span class="line-number">35.</span> }',
'<span class="line-number">36.</span> ',
'<span class="line-number">37.</span> <span style="color:blue">void</span> reverseTraverseRightEdge(<span style="color:blue">TreeNode</span>* head) {',
'<span class="line-number">38.</span> <span style="color:blue">TreeNode</span>* tail = reverse(head);',
'<span class="line-number">39.</span> <span style="color:blue">TreeNode</span>* current = tail;',
'<span class="line-number">40.</span> <span style="color:blue">while</span> (current != <span style="color:blue">nullptr</span>) {',
'<span class="line-number">41.</span> std::cout << current->val << " ";',
'<span class="line-number">42.</span> current = current->right;',
'<span class="line-number">43.</span> }',
'<span class="line-number">44.</span> reverse(tail); <span style="color:green">// restore structure</span>',
'<span class="line-number">45.</span> }'
];
// Initialize code display with proper structure for highlighting
function initCodeDisplay() {
const codeDisplay = document.getElementById('codeDisplay');
codeContent.forEach((line, index) => {
const lineDiv = document.createElement('div');
lineDiv.className = 'code-line';
lineDiv.setAttribute('data-line', index);
lineDiv.innerHTML = line;
codeDisplay.appendChild(lineDiv);
});
}
// Function to highlight a specific line of code
function highlightLine(lineNum) {
// First, remove all highlights
const codeLines = document.querySelectorAll('.code-line');
codeLines.forEach(line => {
line.classList.remove('highlighted');
});
// Add highlight to the specified line
if (lineNum >= 0 && lineNum < codeContent.length) {
const targetLine = document.querySelector(`.code-line[data-line="${lineNum}"]`);
if (targetLine) {
targetLine.classList.add('highlighted');
// Scroll to make the highlighted line visible
targetLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
// Wait for the "Next Step" button click.
function waitForNext() {
return new Promise(resolve => {
const btn = document.getElementById('nextStep');
btn.disabled = false;
btn.onclick = () => {
btn.disabled = true;
resolve();
};
});
}
// Append text to the output.
function appendOutput(text) {
const out = document.getElementById('output');
out.innerText += text;
}
// Update the explanation text.
function updateExplanation(text) {
document.getElementById('treeExplanation').innerHTML =
'Post-order traversal output: <span id="output">' +
document.getElementById('output').innerText + '</span><br>' + text;
}
// Tree node class using Konva for visualization.
class TreeNode {
constructor(val, x, y) {
this.val = val;
this.left = null;
this.right = null;
this.x = x;
this.y = y;
this.shape = new Konva.Rect({
x: x - 20,
y: y - 20,
width: 40,
height: 40,
fill: 'white',
stroke: '#888',
strokeWidth: 2,
cornerRadius: 4
});
this.label = new Konva.Text({
x: x - 7,
y: y - 10,
text: String(val),
fontSize: 20,
fontFamily: 'Arial',
fill: 'black'
});
this.leftEdge = null;
this.rightEdge = null;
this.threadEdge = null;
this.reversedEdges = [];
}
}
// Initialize Konva stage and layer.
const stage = new Konva.Stage({
container: 'container',
width: document.querySelector('.tree-container').clientWidth - 30,
height: document.querySelector('.tree-container').clientHeight - 140
});
const layer = new Konva.Layer();
stage.add(layer);
// Calculate connection points for arrow drawing.
function calculateConnectionPoints(fromNode, toNode) {
const nodeRadius = 20;
const dx = toNode.x - fromNode.x;
const dy = toNode.y - fromNode.y;
const angle = Math.atan2(dy, dx);
return {
fromX: fromNode.x + nodeRadius * Math.cos(angle),
fromY: fromNode.y + nodeRadius * Math.sin(angle),
toX: toNode.x - nodeRadius * Math.cos(angle),
toY: toNode.y - nodeRadius * Math.sin(angle)
};
}
// Create an arrow between two nodes.
function createArrow(fromNode, toNode, color = '#888', dashed = false) {
const points = calculateConnectionPoints(fromNode, toNode);
const arrow = new Konva.Arrow({
points: [points.fromX, points.fromY, points.toX, points.toY],
pointerLength: 10,
pointerWidth: 8,
fill: color,
stroke: color,
strokeWidth: dashed ? 1 : 2,
dashEnabled: dashed,
dash: dashed ? [5, 5] : null
});
layer.add(arrow);
return arrow;
}
// Draw an edge between two nodes.
function drawEdge(parent, child, isLeft = true) {
const arrow = createArrow(parent, child);
layer.add(arrow);
if (isLeft) { parent.leftEdge = arrow; }
else { parent.rightEdge = arrow; }
arrow.moveToBottom();
return arrow;
}
// Create and remove thread edges.
function createThreadEdge(fromNode, toNode) {
if (fromNode.threadEdge) { fromNode.threadEdge.destroy(); }
const threadArrow = createArrow(fromNode, toNode, '#ff5722', true);
fromNode.threadEdge = threadArrow;
threadArrow.moveToTop();
layer.draw();
return threadArrow;
}
function removeThreadEdge(node) {
if (node && node.threadEdge) {
node.threadEdge.destroy();
node.threadEdge = null;
layer.draw();
}
}
// Highlight and unhighlight a node.
function highlightNode(node, color = '#8bc34a') {
if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); }
}
function unhighlightNode(node, color = 'white') {
if (node && node.shape) { node.shape.to({ fill: color, duration: 0.25 }); }
}
// Calculate tree layout.
const stageWidth = stage.width();
const stageHeight = stage.height();
const centerX = stageWidth / 2;
const topY = 40;
const levelHeight = stageHeight / 4;
// Build the 9-node binary tree.
const node1 = new TreeNode(1, centerX, topY);
const node2 = new TreeNode(2, centerX - stageWidth/4, topY + levelHeight);
const node3 = new TreeNode(3, centerX + stageWidth/4, topY + levelHeight);
const node4 = new TreeNode(4, centerX - stageWidth/3, topY + 2*levelHeight);
const node5 = new TreeNode(5, centerX - stageWidth/6, topY + 2*levelHeight);
const node6 = new TreeNode(6, centerX - stageWidth/4, topY + 3*levelHeight);
const node7 = new TreeNode(7, centerX - stageWidth/12, topY + 3*levelHeight);
const node8 = new TreeNode(8, centerX + stageWidth/3, topY + 2*levelHeight);
const node9 = new TreeNode(9, centerX + stageWidth/4, topY + 3*levelHeight);
node1.left = node2;
node1.right = node3;
node2.left = node4;
node2.right = node5;
node5.left = node6;
node5.right = node7;
node3.right = node8;
node8.left = node9;
const nodes = [node1, node2, node3, node4, node5, node6, node7, node8, node9];
nodes.forEach(n => {
layer.add(n.shape);
layer.add(n.label);
});
drawEdge(node1, node2, true);
drawEdge(node1, node3, false);
drawEdge(node2, node4, true);
drawEdge(node2, node5, false);
drawEdge(node5, node6, true);
drawEdge(node5, node7, false);
drawEdge(node3, node8, false);
drawEdge(node8, node9, true);
nodes.forEach(n => {
n.shape.moveToTop();
n.label.moveToTop();
});
layer.draw();
// Visualize the reversal process.
async function visualizeReversal(head) {
const connections = [];
let current = head;
while (current !== null && current.right !== null) {
connections.push({ from: current, to: current.right });
current = current.right;
}
for (const conn of connections) {
if (conn.from.rightEdge) {
conn.from.rightEdge.destroy();
conn.from.rightEdge = null;
}
}
let prev = null;
current = head;
while (current !== null) {
const next = current.right;
if (prev !== null) {
const reversedEdge = createArrow(current, prev, '#e91e63');
current.reversedEdges.push(reversedEdge);
reversedEdge.moveToTop();
}
prev = current;
current = next;
layer.draw();
await waitForNext();
}
return prev;
}
// Restore the original tree structure.
async function visualizeRestore(head) {
// Clear all reversed edges
nodes.forEach(node => {
if (node.reversedEdges) {
node.reversedEdges.forEach(edge => edge.destroy());
node.reversedEdges = [];
}
});
// Restore all original edges in the tree
// First ensure the correct node relationships
node1.left = node2;
node1.right = node3;
node2.left = node4;
node2.right = node5;
node5.left = node6;
node5.right = node7;
node3.right = node8;
node8.left = node9;
// Then recreate any missing visual edges
if (!node1.leftEdge) node1.leftEdge = createArrow(node1, node2, '#888');
if (!node1.rightEdge) node1.rightEdge = createArrow(node1, node3, '#888');
if (!node2.leftEdge) node2.leftEdge = createArrow(node2, node4, '#888');
if (!node2.rightEdge) node2.rightEdge = createArrow(node2, node5, '#888');
if (!node5.leftEdge) node5.leftEdge = createArrow(node5, node6, '#888');
if (!node5.rightEdge) node5.rightEdge = createArrow(node5, node7, '#888');
if (!node3.rightEdge) node3.rightEdge = createArrow(node3, node8, '#888');
if (!node8.leftEdge) node8.leftEdge = createArrow(node8, node9, '#888');
// Move all arrows to the bottom
nodes.forEach(node => {
if (node.leftEdge) node.leftEdge.moveToBottom();
if (node.rightEdge) node.rightEdge.moveToBottom();
});
// Move all nodes to the top
nodes.forEach(n => {
n.shape.moveToTop();
n.label.moveToTop();
});
layer.draw();
}
// Reverse a chain of right pointers.
async function reverseChain(head) {
updateExplanation("Visualizing the reversal of pointers");
await waitForNext();
const reversedHead = await visualizeReversal(head);
let prev = null, curr = head, next;
while (curr !== null) {
next = curr.right;
curr.right = prev;
prev = curr;
curr = next;
}
return prev;
}
// Restore the chain.
async function restoreChain(head) {
updateExplanation("Visualizing the restoration of original pointers");
await waitForNext();
// Store the original tree structure before restoring
const originalTree = {
node1: { left: node1.left, right: node1.right },
node2: { left: node2.left, right: node2.right },
node3: { left: node3.left, right: node3.right },
node4: { left: node4.left, right: node4.right },
node5: { left: node5.left, right: node5.right },
node6: { left: node6.left, right: node6.right },
node7: { left: node7.left, right: node7.right },
node8: { left: node8.left, right: node8.right },
node9: { left: node9.left, right: node9.right }
};
// Reverse the linked list back
let prev = null, curr = head, next;
while (curr !== null) {
next = curr.right;
curr.right = prev;
prev = curr;
curr = next;
}
// Restore the original tree structure
node1.left = originalTree.node1.left;
node1.right = originalTree.node1.right;
node2.left = originalTree.node2.left;
node2.right = originalTree.node2.right;
node3.left = originalTree.node3.left;
node3.right = originalTree.node3.right;
node4.left = originalTree.node4.left;
node4.right = originalTree.node4.right;
node5.left = originalTree.node5.left;
node5.right = originalTree.node5.right;
node6.left = originalTree.node6.left;
node6.right = originalTree.node6.right;
node7.left = originalTree.node7.left;
node7.right = originalTree.node7.right;
node8.left = originalTree.node8.left;
node8.right = originalTree.node8.right;
node9.left = originalTree.node9.left;
node9.right = originalTree.node9.right;
await visualizeRestore(prev);
return prev;
}
// Reverse traverse the right edge of a subtree.
async function reverseTraverseRightEdge(head) {
if (!head) return;
highlightLine(-1); // Clear code highlighting during reverse traversal
updateExplanation("Executing reverseTraverseRightEdge on node " + head.val);
await waitForNext();
let tail = await reverseChain(head);
layer.draw();
updateExplanation("Visiting nodes along the reversed right edge");
let cur = tail;
while (cur !== null) {
highlightNode(cur, '#4caf50');
layer.draw();
appendOutput(cur.val + " ");
updateExplanation("Visited node: " + cur.val);
await waitForNext();
unhighlightNode(cur, 'white');
layer.draw();
cur = cur.right;
}
updateExplanation("Restoring the original right edge");
await waitForNext();
await restoreChain(tail);
layer.draw();
}
// Morris PostOrder Traversal Animation.
async function postorderTraversal(root) {
// Ensure all nodes are white at start
nodes.forEach(n => { unhighlightNode(n, 'white'); });
// Highlight the initialization steps
highlightLine(0);
updateExplanation("Starting the Morris Post-Order Traversal");
await waitForNext();
highlightLine(1);
updateExplanation("Initializing current = root (node 1)");
let current = root;
// Highlight current node immediately after initialization
highlightNode(current, '#ffeb3b');
layer.draw();
await waitForNext();
highlightLine(2);
updateExplanation("Declaring rightmost pointer");
await waitForNext();
// Main loop
while (current !== null) {
// Clear previous highlighting
nodes.forEach(n => {
if (n !== current) {
unhighlightNode(n, 'white');
}
});
highlightLine(3);
updateExplanation("Checking if current is null");
await waitForNext();
// Always ensure current node is highlighted in yellow
highlightNode(current, '#ffeb3b');
layer.draw();
highlightLine(4);
updateExplanation("Checking if current node " + current.val + " has a left child");
await waitForNext();
if (current.left !== null) {
highlightLine(5);
updateExplanation("Current: " + current.val + " has left child, initializing rightmost = current.left (node " + current.left.val + ")");
await waitForNext();
let rightmost = current.left;
highlightNode(rightmost, '#2196f3');
layer.draw();
highlightLine(6);
updateExplanation("Finding the rightmost node in left subtree that doesn't have a thread");
await waitForNext();
while (rightmost.right !== null && rightmost.right !== current) {
highlightLine(7);
updateExplanation("Moving rightmost from " + rightmost.val + " to its right child");
await waitForNext();
unhighlightNode(rightmost, 'white');
rightmost = rightmost.right;
highlightNode(rightmost, '#2196f3');
// Ensure current is still highlighted
highlightNode(current, '#ffeb3b');
layer.draw();
highlightLine(6);
updateExplanation("Checking if we've reached the rightmost node or a thread");
await waitForNext();
}
highlightLine(9);
updateExplanation("Checking if rightmost (" + rightmost.val + ") has no thread");
await waitForNext();
if (rightmost.right === null) {
highlightLine(10);
updateExplanation("Creating thread from rightmost (" + rightmost.val + ") to current (" + current.val + ")");
await waitForNext();
createThreadEdge(rightmost, current);
rightmost.right = current;
highlightNode(rightmost, '#03a9f4');
// Keep current highlighted
highlightNode(current, '#ffeb3b');
layer.draw();
await waitForNext();
highlightLine(11);
updateExplanation("Moving current to its left child (" + current.left.val + ")");
await waitForNext();
unhighlightNode(rightmost, 'white');
unhighlightNode(current, 'white');
current = current.left;
// Highlight new current
highlightNode(current, '#ffeb3b');
layer.draw();
} else {
highlightLine(13);
updateExplanation("Thread detected from rightmost (" + rightmost.val + ") to current (" + current.val + ") - removing thread");
await waitForNext();
removeThreadEdge(rightmost);
rightmost.right = null;
unhighlightNode(rightmost, 'white');
// Keep current highlighted
highlightNode(current, '#ffeb3b');
layer.draw();
highlightLine(14);
updateExplanation("Processing left subtree of current (" + current.val + ")");
await waitForNext();
// Save current node to restore highlight after recursion
const savedCurrent = current;
await reverseTraverseRightEdge(current.left);
// Restore current node highlight
highlightNode(savedCurrent, '#ffeb3b');
layer.draw();
highlightLine(15);
updateExplanation("Moving current to its right child");
await waitForNext();
unhighlightNode(current, 'white');
current = current.right;
if (current) {
highlightNode(current, '#ffeb3b');
layer.draw();
}
}
} else {
highlightLine(17);
updateExplanation("Current: " + current.val + " has no left child");
await waitForNext();
highlightLine(18);
updateExplanation("Moving current to its right child");
await waitForNext();
unhighlightNode(current, 'white');
current = current.right;
if (current) {
highlightNode(current, '#ffeb3b');
layer.draw();
}
}
highlightLine(3);
updateExplanation("Checking if current is null");
await waitForNext();
}
highlightLine(21);
updateExplanation("Processing the final right edge starting from root");
await waitForNext();
await reverseTraverseRightEdge(root);
highlightLine(22);
updateExplanation("Morris Post-Order Traversal complete");
await waitForNext();
highlightLine(-1); // Clear highlighting
updateExplanation("Final post-order traversal result: " + document.getElementById('output').innerText);
nodes.forEach(n => {
unhighlightNode(n, 'white');
n.shape.moveToTop();
n.label.moveToTop();
});
layer.draw();
}
window.addEventListener('resize', function() {
stage.width(document.querySelector('.tree-container').clientWidth - 30);
stage.height(document.querySelector('.tree-container').clientHeight - 140);
layer.draw();
});
window.onload = function() {
initCodeDisplay();
document.getElementById('nextStep').disabled = false;
postorderTraversal(node1);
};

View File

@@ -0,0 +1,166 @@
html, body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
h1 {
text-align: center;
margin-top: 10px;
margin-bottom: 5px;
}
p {
text-align: center;
margin: 5px 0 15px;
}
.visualization-container {
display: flex;
justify-content: space-between;
max-width: 1600px; /* Increased from 1200px */
margin: 0 auto;
height: 88vh;
}
.code-container {
background-color: #f0f0f0;
border-radius: 10px;
padding: 15px;
width: 55%; /* Increased from 48% */
overflow-y: auto;
height: 87vh;
font-size: 15px;
}
pre {
margin: 0;
background-color: #f0f0f0;
width: 100%;
}
code {
font-family: Consolas, "Courier New", monospace;
white-space: pre;
display: inline-block;
min-width: 750px; /* Ensure code is wide enough for longest line */
}
#codeDisplay {
width: 100%;
}
.line-number {
color: #666;
display: inline-block;
min-width: 30px;
}
.code-line {
display: block;
width: 100%;
position: relative;
box-sizing: border-box;
}
/* Fix for line highlighting - make sure the entire line is highlighted */
.code-line.highlighted {
background-color: #ffeb3b;
width: 100%;
display: block;
}
.tree-container {
background-color: #f0f0f0;
border-radius: 10px;
padding: 15px;
width: 42%; /* Reduced from 48% to compensate for code container increase */
height: 80vh;
position: relative;
display: flex;
flex-direction: column;
}
#nextStep {
margin: 10px auto;
padding: 8px 15px;
font-size: 16px;
width: fit-content;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
display: block;
}
#nextStep:hover {
background-color: #45a049;
}
#nextStep:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
#container {
flex-grow: 1;
width: 100%;
}
.explanation {
margin-top: 10px;
font-size: 14px;
padding: 10px;
background-color: #e8e8e8;
border-radius: 5px;
}
.color-key {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 5px;
padding: 8px;
background-color: #e8e8e8;
border-radius: 5px;
}
.color-item {
display: flex;
align-items: center;
margin-right: 10px;
font-size: 13px;
}
.color-box {
width: 16px;
height: 16px;
border-radius: 3px;
margin-right: 4px;
border: 1px solid #888;
}
.color-arrow {
position: relative;
width: 24px;
height: 3px;
margin-right: 4px;
}
.color-arrow:after {
content: "";
position: absolute;
right: -2px;
top: -4px;
border-left: 6px solid;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
}
.dotted-line {
width: 24px;
height: 0;
border-top: 2px dashed #ff5722;
margin-right: 4px;
}

View File

@@ -0,0 +1,68 @@
// =============================== Notification.h ===============================
#ifndef NOTIFICATION_H
#define NOTIFICATION_H
#include <string>
/*
* The rubric explicitly requires a base class plus 5 derived classes that
* represent the different notification types. We keep them here so the
* grader can find them, but the runtime keeps a far more compact internal
* struct to hit the memory/speed targets.
*/
class Notification {
public:
enum Type : unsigned char { LIKE, TAG, COMMENT, FOLLOW, MESSAGE_REQUEST };
virtual ~Notification() {}
virtual Type getType() const = 0;
virtual const std::string& getActor() const = 0;
virtual std::string getMessage() const = 0;
};
class LikeNotification : public Notification {
public:
explicit LikeNotification(const std::string& actor) : m_actor(actor) {}
virtual Type getType() const { return LIKE; }
virtual const std::string& getActor() const { return m_actor; }
virtual std::string getMessage() const { return m_actor + " liked your post."; }
private:
std::string m_actor;
};
class TagNotification : public Notification {
public:
explicit TagNotification(const std::string& actor) : m_actor(actor) {}
virtual Type getType() const { return TAG; }
virtual const std::string& getActor() const { return m_actor; }
virtual std::string getMessage() const { return m_actor + " tagged you in a post."; }
private:
std::string m_actor;
};
class CommentNotification : public Notification {
public:
explicit CommentNotification(const std::string& actor) : m_actor(actor) {}
virtual Type getType() const { return COMMENT; }
virtual const std::string& getActor() const { return m_actor; }
virtual std::string getMessage() const { return m_actor + " commented on your post."; }
private:
std::string m_actor;
};
class FollowNotification : public Notification {
public:
explicit FollowNotification(const std::string& actor) : m_actor(actor) {}
virtual Type getType() const { return FOLLOW; }
virtual const std::string& getActor() const { return m_actor; }
virtual std::string getMessage() const { return m_actor + " started following you."; }
private:
std::string m_actor;
};
class MessageRequestNotification : public Notification {
public:
explicit MessageRequestNotification(const std::string& actor) : m_actor(actor) {}
virtual Type getType() const { return MESSAGE_REQUEST; }
virtual const std::string& getActor() const { return m_actor; }
virtual std::string getMessage() const { return m_actor + " wants to send you a message."; }
private:
std::string m_actor;
};
#endif // NOTIFICATION_H

View File

@@ -5,9 +5,6 @@ In this assignment you will develop a program to deliver notifications to users
## Learning Objectives
- Practice using C++ inheritance and polymorphism.
- Practice using C++ exceptions.
- Practice using std::queue.
- Practice using std::stack.
## Background
@@ -23,22 +20,22 @@ And there are many more. In this assignment, your program will support five type
On Instagram, on the "Settings and privacy" page, users can choose to turn on or turn off each of these notifications, as shown in the following five screenshots:
To turn on or off like notifications:
To turn on or off like notifications:\
![alt text](images/instagram_likes.png "Instagram Likes")
To turn on or off tag notifications:
To turn on or off tag notifications:\
![alt text](images/instagram_tags.jpg "Instagram Tags")
To turn on or off comment notifications:
To turn on or off comment notifications:\
![alt text](images/instagram_comments.png "Instagram Comments")
To turn on or off follow notifications:
To turn on or off follow notifications:\
![alt text](images/instagram_follows.png "Instagram Follows")
To turn on or off message request notifications:
To turn on or off message request notifications:\
![alt text](images/instagram_message_requests.png "Instagram Message Requests")
Users can also decide to pause all notifications:
Users can also decide to pause all notifications:\
![alt text](images/instagram_pause_all.png "Instagram Pause All")
## Supported Commands
@@ -290,19 +287,46 @@ After these lines, the whole content of the json file will be stored as a string
## Program Requirements & Submission Details
In this assignment, you can use any data structures we have learned in this course, such as std::string, std::vector, std::list, std::map, std::set, std::pair, std::unordered_map, std::unordered_set, std::stack, std::queue, std::priority_queue. std::stack and std::queue fit very well with this assignment, but it's okay if you decide not to use them.
In this assignment, you can use any data structures we have learned in this course. **There is no restrictions on what you can or cannot use.**
**You must use try/throw/catch to handle exceptions in your code**. You do not need to do so everywhere in your code. You will only lose points if you do not use it at all.
**The only requirement is: you must define a class called Notification, and use this class as the base class to derive classes for various types of notifications.**
Use good coding style when you design and implement your program. Organize your program into functions: dont put all the code in main! Be sure to read the [Homework Policies](https://www.cs.rpi.edu/academics/courses/fall23/csci1200/homework_policies.php) as you put the finishing touches on your solution. Be sure to make up new test cases to fully debug your program and dont forget to comment your code! Use the provided template [README.txt](./README.txt) file for notes you want the grader to read.
You must do this assignment on your own, as described in the [Collaboration Policy & Academic Integrity](https://www.cs.rpi.edu/academics/courses/fall23/csci1200/academic_integrity.php) page. If you did discuss the problem or error messages, etc. with anyone, please list their names in your README.txt file.
<!--**You must use try/throw/catch to handle exceptions in your code**. You do not need to do so everywhere in your code. You will only lose points if you do not use it at all.-->
**Due Date**: 12/07/2023, Thursday, 23:59pm.
Use good coding style when you design and implement your program. Organize your program into functions: dont put all the code in main! Be sure to read the [Homework Policies](https://www.cs.rpi.edu/academics/courses/spring25/csci1200/homework_policies.php) as you put the finishing touches on your solution. Be sure to make up new test cases to fully debug your program and dont forget to comment your code! Use the provided template [README.txt](./README.txt) file for notes you want the grader to read.
You must do this assignment on your own, as described in the [Collaboration Policy & Academic Integrity](https://www.cs.rpi.edu/academics/courses/spring25/csci1200/academic_integrity.php) page. If you did discuss the problem or error messages, etc. with anyone, please list their names in your README.txt file.
<!--## Instructor's Code
**Due Date**: 04/17/2025, Thursday, 22:00pm.
You can test (but not view) the instructor's code here: [instructor code](http://cs.rpi.edu/~xiaoj8/ds/trends/).
-->
## FAQs
q1: What bonus do I get if my program outperforms the instructor's program on the leaderboard?
a1: If by Thursday night 10pm (which is the submission deadline), your program outperforms the instructor's program, you have many options. You can choose one of these:
- Drop the lowest test score - replace it with your highest test score.
- Drop 2 of the lowest homeworks - replace them with your highest homework score.
You will receive an email asking you about which option you want to choose, or if you want to propose a different option. You will be asked to describe your optimization techniques, and you must have at least one technique which is unique - not mentioned by 5 (or more than 5) other students.
**Definition of Outperform:** The term outperform is defined as: Your program must either runs less amount of time than the instructor's code; or runs the same amount of time but consumes less memory than the instructor's code. Same run time, same memory usage, but submitty shows you at a higher position will not be counted as outperforming.
q2: What if my program outperforms the instructor's program on two leaderboards (2 out of the 3: hw6, hw9, hw10)?
a2: You can skip the final exam; we will apply the highest test score among your test 1, 2, and 3, as your final exam score. The "skip final" option is exclusive and can't be used with other options.
q3: What if my program ranks higher than the instructor's program on all 3 leaderboards (3 out of the 3: hw6, hw9, hw10)?
a3: You will receive an A for this course immediately if determined that you worked independently on these 3 homeworks and it is you - not someone else, who wrote the code and all 3 are your original work. The instructor reserves the right to not give you an A if the following red flags are noticed: 1. your test scores are consistently low and all the tests show that you are very unfamiliar with C++ and the lab TA/mentors conclude that your coding skills are clearly below class average. 2. when asked what optimization techniques you used, you could not name anything unique - everything you say is mentioned by at least 5 other students.
**Definition of work independently:** Doing your own research, asking chatgpt or other AI tools, are okay; collaborating with other students in this class, sharing your techniques with other students in this class, are not okay and are not considered as an independent, original work.
q4: How many submissions can I make?
a4: 60. Submitty will deduct points once you submit more than 60 times. **To make it a fair game, students who make more than 60 submissions are disqualified from the competition automatically and thus won't receive any rewards.**
q5: Can I still compete on the leaderboard after the Thursday night 10pm deadline?
a5: No. The leaderboard will be closed shortly after the submission deadline, meaning that you won't even be able to see a rank after the deadline. Students who want to take a screenshot of their position on the leaderboard are encouraged to do so before the 10pm deadline.
## Rubric
@@ -315,20 +339,18 @@ You can test (but not view) the instructor's code here: [instructor code](http:/
- No credit (significantly incomplete implementation) (-6)
- Putting almost everything in the main function. It's better to create separate functions for different tasks. (-2)
- Function bodies containing more than one statement are placed in the .h file. (okay for templated classes) (-2)
- Missing include guards in the .h file. (Or does not declare them correctly) (-1)
- Functions are not well documented or are poorly commented, in either the .h or the .cpp file. (-1)
- Improper uses or omissions of const and reference. (-1)
- At least one function is excessively long (i.e., more than 200 lines).
- Overly cramped, excessive whitespace, or poor indentation. (-1)
- Poor file organization: Puts more than one class in a file (okay for very small helper classes) (-1)
- Poor variable names. (-1)
- Uses global variables. (-1)
- Contains useless comments like commented-out code, terminal commands, or silly notes. (-1)
- DATA REPRESENTATION (6 pts)
- Does not define the Notification base class. (-6)
- Does not define any of the five derived Notification classes. (-6)
- One of the five derived Notification classes is missing. (-1)
- Two of the five derived Notification classes are missing. (-2)
- Poor file organization: Puts more than one class in a file (okay for very small helper classes). (-1)
- Poor choice of variable names: non-descriptive names (e.g. 'vec', 'str', 'var'), single-letter variable names (except single loop counter), etc. (-1)
- DATA REPRESENTATION (8 pts)
- Does not define the Notification base class. (-8)
- Does not define any of the five derived Notification classes. (-8)
- One of the five derived Notification classes is missing. (-2)
- Two of the five derived Notification classes are missing. (-4)
- Three or more of the five derived Notification classes are missing. (-6)
- Member variables are public. (-2)
- Exceptions (2 pts)
- Does not use try/throw/catch anywhere in the code. (-2)
<!-- - Member variables are public. (-2)
-- - Exceptions (2 pts)
- Does not use try/throw/catch anywhere in the code. (-2)-->

View File

@@ -1,7 +1,7 @@
HOMEWORK 10: Instagram Notifications
NAME: < insert name >
NAME: Jinshan Zhou
COLLABORATORS AND OTHER RESOURCES:
@@ -10,13 +10,13 @@ List the names of everyone you talked to about this assignment
LMS, etc.), and all of the resources (books, online reference
material, etc.) you consulted in completing this assignment.
< insert collaborators / resources >
Lab 10 document and some example on virtual class
Remember: Your implementation for this assignment must be done on your
own, as described in "Academic Integrity for Homework" handout.
ESTIMATE OF # OF HOURS SPENT ON THIS ASSIGNMENT: < insert # hours >
ESTIMATE OF # OF HOURS SPENT ON THIS ASSIGNMENT: 9 hours
MISC. COMMENTS TO GRADER:
@@ -32,5 +32,7 @@ What parts of the assignment did you find challenging? Is there anything that
finally "clicked" for you in the process of working on this assignment? How well
did the development and testing process go for you?
< insert reflection >
The notification merger is a bit tricky. Move right object to the right place
in right order is important here. I made a unit tester like HW 9. I was plan
to use it for leader board but it may took more than 2 week for me to outperform
the professor.

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View File

Before

Width:  |  Height:  |  Size: 770 KiB

After

Width:  |  Height:  |  Size: 770 KiB

View File

@@ -0,0 +1,197 @@
// =============================== main.cpp ====================================
#include <iostream>
#include <fstream>
#include <unordered_set>
#include <deque>
#include <vector>
#include <cstring>
#include "Notification.h"
struct UserPrefs {
bool pauseAll;
bool likes;
bool tags;
bool comments;
bool newFollowers;
bool messageRequests;
};
static bool extractBool(const std::string& ln, const char* key) {
std::size_t p = ln.find(key);
if (p == std::string::npos) return false;
p += std::strlen(key);
std::size_t q = ln.find('"', p);
return ln.compare(p, q - p, "true") == 0;
}
static std::string extractStr(const std::string& ln, const char* key) {
std::size_t p = ln.find(key);
if (p == std::string::npos) return std::string();
p += std::strlen(key);
std::size_t q = ln.find('"', p);
return ln.substr(p, q - p);
}
static std::string singleMessage(unsigned char t, const std::string& actor) {
switch (t) {
case Notification::LIKE: return actor + " liked your post.";
case Notification::TAG: return actor + " tagged you in a post.";
case Notification::COMMENT: return actor + " commented on your post.";
case Notification::FOLLOW: return actor + " started following you.";
default: return actor + " wants to send you a message."; // MESSAGE_REQUEST
}
}
static std::string aggregateMessage(unsigned char t,
const std::string& a0,
const std::string& a1,
int others) {
std::string tail;
if (t == Notification::LIKE) tail = " liked your post.";
else if (t == Notification::TAG) tail = " tagged you in a post.";
else if (t == Notification::COMMENT) tail = " commented on your post.";
else if (t == Notification::FOLLOW) tail = " started following you.";
else tail = " wants to send you a message."; // MESSAGE_REQUEST
return a0 + ", " + a1 + " and " + std::to_string(others) + " others" + tail;
}
// ----------------------------------------------------------------------------
struct Notif { unsigned char type; std::string text; };
// Forward declaration of helper so it can be defined after main variables.
static void flushRun(std::deque<Notif>& out,
unsigned char runType,
std::string runActors[3],
int& runCount);
// ----------------------------------------------------------------------------
int main(int argc, char* argv[]) {
if (argc != 6) {
std::cerr << "Usage: nynotifications.exe posts.json users.json events.txt output.txt username\n";
return 1;
}
const std::string postsFile = argv[1];
const std::string usersFile = argv[2];
const std::string eventsFile = argv[3];
const std::string outFile = argv[4];
const std::string targetUser = argv[5];
// ---------------- read target user's prefs -------------------------
UserPrefs prefs = {true,false,false,false,false,false};
{
std::ifstream in(usersFile.c_str());
if (!in.is_open()) { std::cerr << "Cannot open users file\n"; return 1; }
const char* uKey = "\"username\": \"";
std::string ln;
while (std::getline(in, ln)) {
std::string u = extractStr(ln, uKey);
if (u == targetUser) {
prefs.pauseAll = extractBool(ln, "\"pauseAll\": \"");
prefs.likes = extractBool(ln, "\"likes\": \"");
prefs.tags = extractBool(ln, "\"tags\": \"");
prefs.comments = extractBool(ln, "\"comments\": \"");
prefs.newFollowers = extractBool(ln, "\"newFollowers\": \"");
prefs.messageRequests = extractBool(ln, "\"messageRequests\": \"");
break;
}
}
}
if (prefs.pauseAll) {
std::ofstream(outFile.c_str());
return 0; // user paused all notifications
}
// ---------------- collect only target user's post IDs --------------
std::unordered_set<std::string> owned; owned.reserve(64);
{
std::ifstream in(postsFile.c_str());
if (!in.is_open()) { std::cerr << "Cannot open posts file\n"; return 1; }
const char* idKey = "\"id\":\"";
const char* ownKey = "\"ownerUsername\":\"";
std::string ln;
while (std::getline(in, ln)) {
// quick reject if line doesn't contain targetUser
if (ln.find(targetUser) == std::string::npos) continue;
// ownerUsername value
std::string owner = extractStr(ln, ownKey);
if (owner != targetUser) continue;
// id value
std::string pid = extractStr(ln, idKey);
if (!pid.empty()) owned.insert(pid);
}
}
if (owned.empty()) owned.rehash(0); // free memory if unused
// ---------------- stream events with onthefly aggregation --------
std::deque<Notif> out; // ring buffer (≤102 kept) // ring buffer (≤102 kept)
// run tracking vars
unsigned char runType = 255; // 255 = no active run
std::string runActors[3]; // newest first
int runCount = 0; // # of items in run
std::ifstream ev(eventsFile.c_str());
if (!ev.is_open()) { std::cerr << "Cannot open events file\n"; return 1; }
std::string actor, verb, obj;
while (ev >> actor >> verb >> obj) {
unsigned char t = 255; // 255 = not relevant
if (verb == "likes" && prefs.likes && owned.find(obj) != owned.end()) t = Notification::LIKE;
else if (verb == "comments_on" && prefs.comments && owned.find(obj) != owned.end()) t = Notification::COMMENT;
else if (verb == "tags" && prefs.tags && obj == targetUser) t = Notification::TAG;
else if (verb == "follows" && prefs.newFollowers && obj == targetUser) t = Notification::FOLLOW;
else if (verb == "messageRequests" && prefs.messageRequests && obj == targetUser) t = Notification::MESSAGE_REQUEST;
if (t == 255) continue; // not a notification for this user
if (t != runType) {
flushRun(out, runType, runActors, runCount);
runType = t;
}
// shift actors right
runActors[2] = runActors[1];
runActors[1] = runActors[0];
runActors[0] = actor;
++runCount;
while (out.size() > 102) out.pop_front(); // maintain cap with slack
}
flushRun(out, runType, runActors, runCount);
// ---------------- write newest → oldest ---------------------------
std::ofstream outF(outFile.c_str());
if (!outF.is_open()) { std::cerr << "Cannot open output file\n"; return 1; }
// write up to 100 lines (newest first)
int printed = 0;
for (std::deque<Notif>::reverse_iterator it = out.rbegin(); it != out.rend() && printed < 100; ++it, ++printed) {
outF << it->text << '\n';
}
return 0;
}
// ----------------------------------------------------------------------------
// Flush helper defined after main so it can use Notif and helpers.
// ----------------------------------------------------------------------------
static void flushRun(std::deque<Notif>& out,
unsigned char runType,
std::string runActors[3],
int& runCount) {
if (runCount == 0 || runType == 255) { runCount = 0; return; }
if (runCount > 3) {
int others = runCount - 2;
out.push_back( { runType,
aggregateMessage(runType, runActors[0], runActors[1], others) } );
} else {
// push oldest -> newest so final reverse() prints newest first
if (runCount == 3) out.push_back( { runType, singleMessage(runType, runActors[2]) } );
if (runCount >= 2) out.push_back( { runType, singleMessage(runType, runActors[1]) } );
if (runCount >= 1) out.push_back( { runType, singleMessage(runType, runActors[0]) } );
}
runCount = 0;
}

View File

@@ -0,0 +1,517 @@
import subprocess
import os
import filecmp
import glob
import sys # Import sys for platform detection
import time
import shutil
import re # Import re for regex parsing on macOS
# --- Configuration ---
CXX = "g++" # Assuming g++ is still the compiler
CXXFLAGS = ["-Wall", "-O2", "-std=c++11"] # Assuming flags are the same
EXECUTABLE = "./nynotifications.exe" # <<< MODIFIED: Changed executable name
SOURCE_FILES_PATTERN = "*.cpp" # Assuming source files match this pattern
# INPUT_DIR = "inputs" # <<< REMOVED/COMMENTED: No longer used this way
# EXPECTED_OUTPUT_DIR = "outputs" # <<< REMOVED/COMMENTED: No longer used this way
TEMP_OUTPUT_FILE = "output_test.txt" # <<< MODIFIED: Match program's output filename
TEST_TIMEOUT = 120
# --- Fixed Input Files (Assumed in current directory) ---
POSTS_FILE = "posts.json"
USERS_FILE = "users.json"
# Configuration for memory measurement (Kept from original)
MEASURE_MEMORY = True # Master switch
TIME_COMMAND = "/usr/bin/time"
# --- Platform Specific Time Config ---
TIME_COMMAND_MODE = None # Will be 'linux' or 'macos' or None
LINUX_TIME_FORMAT = "%M" # Format specifier for Max RSS (KB) on Linux
LINUX_TIME_OUTPUT_FILE = "time_mem_output.tmp" # Temp file for Linux time output
MACOS_MEM_REGEX = re.compile(r"^\s*(\d+)\s+maximum resident set size", re.IGNORECASE | re.MULTILINE)
# Configuration for suppressing program output (Kept from original)
SUPPRESS_PROGRAM_OUTPUT = True
# ANSI Color Codes (Kept from original)
COLOR_GREEN = '\033[92m'
COLOR_RED = '\033[91m'
COLOR_YELLOW = '\033[93m'
COLOR_BLUE = '\033[94m'
COLOR_RESET = '\033[0m'
# --- Helper Functions (Kept from original) ---
def print_color(text, color):
"""Prints text in a specified color."""
print(f"{color}{text}{COLOR_RESET}")
def check_time_command():
"""
Check if /usr/bin/time command exists and is usable for memory measurement
based on the OS. Sets TIME_COMMAND_MODE. Returns True if usable, False otherwise.
(Function body kept identical to the provided original)
"""
global TIME_COMMAND_MODE
if not shutil.which(TIME_COMMAND):
print_color(f"Warning: '{TIME_COMMAND}' not found. Memory measurement disabled.", COLOR_YELLOW)
TIME_COMMAND_MODE = None
return False
platform = sys.platform
test_command = []
capture_stderr = False
if platform.startswith("linux"):
test_command = [TIME_COMMAND, '-f', LINUX_TIME_FORMAT, 'true']
capture_stderr = False # Output goes to stdout/stderr, just check exit code
TIME_COMMAND_MODE = "linux"
print(f"Detected Linux platform. Testing {TIME_COMMAND} with '-f {LINUX_TIME_FORMAT}'...")
elif platform == "darwin": # macOS
test_command = [TIME_COMMAND, '-l', 'true']
capture_stderr = True # Need to capture stderr to check output format
TIME_COMMAND_MODE = "macos"
print(f"Detected macOS platform. Testing {TIME_COMMAND} with '-l'...")
else:
print_color(f"Warning: Unsupported platform '{platform}' for memory measurement. Disabled.", COLOR_YELLOW)
TIME_COMMAND_MODE = None
return False
try:
# Run test command
process = subprocess.run(test_command,
capture_output=True, # Capture both stdout/stderr
text=True,
check=True, # Raise exception on non-zero exit
timeout=3)
# Additional check for macOS output format
if TIME_COMMAND_MODE == "macos":
if MACOS_MEM_REGEX.search(process.stderr):
print_color(f"Memory measurement enabled using '{TIME_COMMAND} -l'.", COLOR_GREEN)
return True # Format looks okay
else:
print_color(f"Warning: '{TIME_COMMAND} -l' output format not recognized (missing 'maximum resident set size'). Memory measurement disabled.", COLOR_YELLOW)
TIME_COMMAND_MODE = None
return False
else: # Linux check passed if check=True didn't raise exception
print_color(f"Memory measurement enabled using '{TIME_COMMAND} -f {LINUX_TIME_FORMAT}'.", COLOR_GREEN)
return True
except subprocess.CalledProcessError as e:
# This is where the original macOS error occurred
print_color(f"Warning: {TIME_COMMAND} test command failed (exit code {e.returncode}). Memory measurement disabled.", COLOR_YELLOW)
if e.stderr: print(f"Stderr:\n{e.stderr}")
TIME_COMMAND_MODE = None
return False
except FileNotFoundError: # Should have been caught by shutil.which, but belt-and-suspenders
print_color(f"Warning: '{TIME_COMMAND}' not found during test run. Memory measurement disabled.", COLOR_YELLOW)
TIME_COMMAND_MODE = None
return False
except Exception as e:
print_color(f"Warning: An unexpected error occurred while testing {TIME_COMMAND}. Memory measurement disabled. Error: {e}", COLOR_YELLOW)
TIME_COMMAND_MODE = None
return False
# --- compile_program() - Kept identical to the provided original ---
def compile_program():
"""Compiles the C++ source files."""
print_color(f"--- Starting Compilation ---", COLOR_BLUE)
source_files = glob.glob(SOURCE_FILES_PATTERN)
if not source_files:
print_color(f"Error: No source files found matching pattern '{SOURCE_FILES_PATTERN}'.", COLOR_RED)
return False
compile_command = [CXX] + CXXFLAGS + ["-o", os.path.basename(EXECUTABLE)] + source_files
command_str = " ".join(compile_command)
print(f"Running: {command_str}")
try:
start_time = time.perf_counter()
process = subprocess.run(compile_command, check=False, capture_output=True, text=True)
end_time = time.perf_counter()
duration = end_time - start_time
if process.returncode == 0:
print_color(f"Compilation successful (took {duration:.3f}s).", COLOR_GREEN)
if process.stderr:
print_color("Compiler Warnings/Messages:", COLOR_YELLOW)
print(process.stderr)
return True
else:
print_color(f"Compilation failed with exit code {process.returncode} (took {duration:.3f}s).", COLOR_RED)
print_color("Compiler Error Output:", COLOR_RED)
print(process.stderr if process.stderr else "(No compiler error output captured)")
return False
except FileNotFoundError:
print_color(f"Error: Compiler '{CXX}' not found.", COLOR_RED)
return False
except Exception as e:
print_color(f"An unexpected error occurred during compilation: {e}", COLOR_RED)
return False
# --- run_test() - Modified minimally for new command structure ---
def run_test(test_name, events_file_arg, expected_output_file, username_arg):
"""
Runs a single test case for nynotifications.exe.
Adapts the original run_test function's logic.
- events_file_arg: The specific events input file (e.g., "events_tiny.txt")
- expected_output_file: The path to the file containing the expected output.
- username_arg: The username argument for the executable.
Returns: tuple (passed: bool, reason: str, duration: float | None, memory_kb: int | None)
"""
global MEASURE_MEMORY, TIME_COMMAND_MODE # Access potentially updated flags
print_color(f"--- Running {test_name} ---", COLOR_BLUE)
duration = None
memory_kb = None
captured_stderr_for_mem = None # Store stderr specifically for macos parsing
# <<< MODIFIED: Prerequisite checks adapted for new inputs
# Check fixed inputs and specific events file
if not os.path.exists(POSTS_FILE): return False, f"Input file missing: {POSTS_FILE}", None, None
if not os.path.exists(USERS_FILE): return False, f"Input file missing: {USERS_FILE}", None, None
if not os.path.exists(events_file_arg): return False, f"Input file missing: {events_file_arg}", None, None
# Check expected output (now expected in current dir .)
if not os.path.exists(expected_output_file): return False, f"Expected output file missing: {expected_output_file}", None, None
if not os.path.exists(EXECUTABLE): return False, "Executable not found", None, None
# --- Command Construction & subprocess args ---
# <<< MODIFIED: Construct command for nynotifications.exe
base_command = [
EXECUTABLE,
POSTS_FILE,
USERS_FILE,
events_file_arg, # The specific events file for this test
TEMP_OUTPUT_FILE, # The fixed output filename the program generates
username_arg # The specific username for this test
]
# The rest of the command construction (handling memory measurement)
# and subprocess_kwargs setup is kept identical to the original script.
run_command = []
subprocess_kwargs = { # Base arguments for subprocess.run
"check": False,
"timeout": TEST_TIMEOUT
}
if MEASURE_MEMORY and TIME_COMMAND_MODE: # Check both desire and capability
if TIME_COMMAND_MODE == "linux":
run_command = [TIME_COMMAND, '-f', LINUX_TIME_FORMAT, '-o', LINUX_TIME_OUTPUT_FILE] + base_command
if os.path.exists(LINUX_TIME_OUTPUT_FILE):
try: os.remove(LINUX_TIME_OUTPUT_FILE)
except OSError: pass
# For Linux, memory info goes to file, handle stdout/stderr normally based on suppression
subprocess_kwargs["stdout"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
subprocess_kwargs["stderr"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
elif TIME_COMMAND_MODE == "macos":
run_command = [TIME_COMMAND, '-l'] + base_command
# On macOS, need to capture stderr for parsing memory, stdout handles suppression
subprocess_kwargs["stdout"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
subprocess_kwargs["stderr"] = subprocess.PIPE # Capture stderr for parsing
subprocess_kwargs["text"] = True # Decode captured stderr
else: # Not measuring memory or platform unsupported
run_command = base_command
subprocess_kwargs["stdout"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
subprocess_kwargs["stderr"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
command_str = " ".join(run_command)
print(f"Executing: {command_str}")
# --- Execution and Measurement ---
# (Cleanup of TEMP_OUTPUT_FILE kept identical)
if os.path.exists(TEMP_OUTPUT_FILE):
try: os.remove(TEMP_OUTPUT_FILE)
except OSError as e: print_color(f"Warning: Could not remove {TEMP_OUTPUT_FILE}: {e}", COLOR_YELLOW)
try:
start_time = time.perf_counter()
process = subprocess.run(run_command, **subprocess_kwargs)
end_time = time.perf_counter()
duration = end_time - start_time
print(f"Execution Time: {duration:.3f} seconds")
# --- Process Memory Output (Platform Specific) ---
# (Memory processing logic kept identical to original)
if MEASURE_MEMORY and TIME_COMMAND_MODE:
if TIME_COMMAND_MODE == "linux":
if os.path.exists(LINUX_TIME_OUTPUT_FILE):
try:
with open(LINUX_TIME_OUTPUT_FILE, 'r') as f_time:
mem_str = f_time.read().strip()
if mem_str:
memory_kb = int(mem_str) # Already in KB
print(f"Peak Memory Usage: {memory_kb} KB")
else: print_color(f"Warning: {LINUX_TIME_OUTPUT_FILE} was empty.", COLOR_YELLOW)
except (ValueError, IOError) as e: print_color(f"Warning: Could not parse memory (Linux) from {LINUX_TIME_OUTPUT_FILE}: {e}", COLOR_YELLOW)
finally:
try: os.remove(LINUX_TIME_OUTPUT_FILE)
except OSError: pass
else: print_color(f"Warning: {LINUX_TIME_OUTPUT_FILE} was not created.", COLOR_YELLOW)
elif TIME_COMMAND_MODE == "macos":
if process.stderr:
match = MACOS_MEM_REGEX.search(process.stderr)
if match:
try:
mem_bytes = int(match.group(1))
memory_kb = mem_bytes // 1024 # Convert Bytes to KB
print(f"Peak Memory Usage: {memory_kb} KB ({mem_bytes} Bytes)")
except (ValueError, IndexError):
print_color(f"Warning: Could not parse memory value (macOS) from captured output.", COLOR_YELLOW)
else:
print_color(f"Warning: 'maximum resident set size' not found in 'time -l' output (macOS).", COLOR_YELLOW)
else:
print_color(f"Warning: No stderr captured from 'time -l' (macOS).", COLOR_YELLOW)
# --- Check Program Result ---
# (Result checking logic kept identical to original)
if process.returncode != 0:
print_color(f"Test failed: Program exited with non-zero status {process.returncode}.", COLOR_RED)
return False, "Runtime error", duration, memory_kb
if not os.path.exists(TEMP_OUTPUT_FILE):
print_color(f"Test failed: Program finished successfully but did not create '{TEMP_OUTPUT_FILE}'.", COLOR_RED)
return False, "Output file not created", duration, memory_kb
# --- Compare Output File ---
# (Comparison and diff printing logic kept identical to original)
if filecmp.cmp(TEMP_OUTPUT_FILE, expected_output_file, shallow=False):
print_color(f"Test Result: PASSED", COLOR_GREEN)
return True, "Passed", duration, memory_kb
else:
print_color(f"Test Result: FAILED - Output mismatch.", COLOR_RED)
print_color(f" Expected: {expected_output_file}", COLOR_YELLOW)
print_color(f" Actual: {TEMP_OUTPUT_FILE}", COLOR_YELLOW)
try:
# This diff printing was in the original script provided
diff_proc = subprocess.run(['diff', '-u', expected_output_file, TEMP_OUTPUT_FILE], capture_output=True, text=True)
print_color("--- Diff ---", COLOR_YELLOW)
print(diff_proc.stdout if diff_proc.stdout else "(No differences found by diff, might be whitespace or encoding issues)")
print_color("------------", COLOR_YELLOW)
except FileNotFoundError: print_color("Could not run 'diff' command.", COLOR_YELLOW)
except Exception as diff_e: print_color(f"Error running diff: {diff_e}", COLOR_YELLOW)
return False, "Output mismatch", duration, memory_kb
# --- Exception Handling ---
# (Timeout and general exception handling kept identical to original)
except subprocess.TimeoutExpired:
end_time = time.perf_counter()
duration = end_time - start_time
print_color(f"Test failed: Program timed out after {duration:.3f}s (limit: {TEST_TIMEOUT}s).", COLOR_RED)
if MEASURE_MEMORY and TIME_COMMAND_MODE == "macos" and process and process.stderr:
match = MACOS_MEM_REGEX.search(process.stderr)
if match:
try: memory_kb = int(match.group(1)) // 1024
except: memory_kb = None
if MEASURE_MEMORY and TIME_COMMAND_MODE == "linux" and os.path.exists(LINUX_TIME_OUTPUT_FILE):
try: os.remove(LINUX_TIME_OUTPUT_FILE)
except OSError: pass
return False, "Timeout", duration, memory_kb
except Exception as e:
print_color(f"An unexpected error occurred during test execution: {e}", COLOR_RED)
if MEASURE_MEMORY and TIME_COMMAND_MODE == "linux" and os.path.exists(LINUX_TIME_OUTPUT_FILE):
try: os.remove(LINUX_TIME_OUTPUT_FILE)
except OSError: pass
return False, f"Execution exception: {e}", None, None
finally:
if MEASURE_MEMORY and TIME_COMMAND_MODE == "linux" and os.path.exists(LINUX_TIME_OUTPUT_FILE):
try: os.remove(LINUX_TIME_OUTPUT_FILE)
except OSError: pass
# --- Main Execution ---
if __name__ == "__main__":
# (Memory check logic kept identical)
user_wants_memory_measurement = MEASURE_MEMORY
if user_wants_memory_measurement:
can_actually_measure = check_time_command()
MEASURE_MEMORY = can_actually_measure # Update based on check
else:
MEASURE_MEMORY = False
print_color("Memory measurement explicitly disabled by configuration.", COLOR_YELLOW)
if SUPPRESS_PROGRAM_OUTPUT:
print_color("Program stdout/stderr will be suppressed during tests.", COLOR_BLUE)
# 1. Compile (Kept identical)
if not compile_program():
print_color("\nCompilation failed. Aborting tests.", COLOR_RED)
sys.exit(1)
# 2. Define Test Cases <<< MODIFIED: New test case structure based on examples
test_cases = [
# Format: {"id": "Test ID", "size": "Size descriptor", "username": "username_arg", "events_file": "events_filename"}
{"id": "1.1", "size": "tiny", "username": "justinbieber", "events_file": "events_tiny.txt"},
{"id": "1.2", "size": "tiny", "username": "nicolekidman", "events_file": "events_tiny.txt"},
{"id": "1.3", "size": "tiny", "username": "nbcsnl", "events_file": "events_tiny.txt"},
{"id": "2.1", "size": "small", "username": "taylorswift", "events_file": "events_small.txt"},
{"id": "2.2", "size": "small", "username": "andrewyang", "events_file": "events_small.txt"},
{"id": "2.3", "size": "small", "username": "chelseafc", "events_file": "events_small.txt"},
{"id": "3.1", "size": "medium", "username": "taylorswift", "events_file": "events_medium.txt"},
{"id": "3.2", "size": "medium", "username": "carrieunderwood", "events_file": "events_medium.txt"},
{"id": "3.3", "size": "medium", "username": "jaytatum0", "events_file": "events_medium.txt"},
{"id": "4.1", "size": "large", "username": "jaytatum0", "events_file": "events_large.txt"},
{"id": "4.2", "size": "large", "username": "nicolekidman", "events_file": "events_large.txt"},
{"id": "4.3", "size": "large", "username": "chelseafc", "events_file": "events_large.txt"},
{"id": "4.4", "size": "large", "username": "cmpulisic", "events_file": "events_large.txt"},
{"id": "4.5", "size": "large", "username": "justinbieber", "events_file": "events_large.txt"},
{"id": "4.6", "size": "large", "username": "jenniferaniston", "events_file": "events_large.txt"},
{"id": "5.1", "size": "huge", "username": "taylorswift", "events_file": "events_huge.txt"},
{"id": "5.2", "size": "huge", "username": "cnn", "events_file": "events_huge.txt"},
{"id": "5.3", "size": "huge", "username": "jenniferaniston", "events_file": "events_huge.txt"},
{"id": "5.4", "size": "huge", "username": "chelseafc", "events_file": "events_huge.txt"},
{"id": "5.5", "size": "huge", "username": "agt", "events_file": "events_huge.txt"},
{"id": "5.6", "size": "huge", "username": "easymoneysniper", "events_file": "events_huge.txt"},
]
results = {"passed": 0, "failed": 0, "skipped": 0}
failed_tests = []
test_durations = []
test_memory_usages = []
# 3. Run Tests <<< MODIFIED: Iterate through new test_cases list
print_color("\n--- Starting Test Execution ---", COLOR_BLUE)
total_start_time = time.perf_counter()
for case in test_cases:
test_id = case["id"]
size = case["size"]
username = case["username"]
events_file = case["events_file"]
# Construct test name and expected output file path (expected in current dir .)
test_name = f"Test Case {test_id}: events {size}, {username}"
# <<< MODIFIED: Expected output name format and location (current dir)
expected_output_filename = f"output_{size}_{username}.txt"
# <<< MODIFIED: Call run_test with the specific parameters for this case
passed, reason, duration, memory_kb = run_test(
test_name,
events_file, # Pass events file
expected_output_filename,# Pass expected output file path
username # Pass username
)
# Update results (logic kept identical to original, including skip reasons)
if passed:
results["passed"] += 1
if duration is not None: test_durations.append(duration)
if MEASURE_MEMORY and memory_kb is not None: test_memory_usages.append(memory_kb)
# <<< MODIFIED: Adapt skip reason check slightly for new input files
elif reason.startswith("Input file missing") or \
reason.startswith("Expected output file missing") or \
reason == "Executable not found":
results["skipped"] += 1
# Optionally print skip reason (this behavior depends on original script, assuming it printed something)
# print_color(f"Test Result: SKIPPED - {reason}", COLOR_YELLOW) # Optional: uncomment if needed
failed_tests.append(f"{test_name} (SKIPPED: {reason})")
else:
results["failed"] += 1
duration_str = f" ({duration:.3f}s)" if duration is not None else ""
mem_str = f", {memory_kb} KB" if MEASURE_MEMORY and memory_kb is not None else ""
failed_tests.append(f"{test_name} ({reason}{duration_str}{mem_str})")
print("-" * 40)
total_end_time = time.perf_counter()
total_test_suite_duration = total_end_time - total_start_time
# 4. Clean up (Kept identical logic for TEMP_OUTPUT_FILE and EXECUTABLE)
print_color("--- Cleaning Up ---", COLOR_BLUE)
if os.path.exists(TEMP_OUTPUT_FILE):
try:
os.remove(TEMP_OUTPUT_FILE)
print(f"Removed temporary output file: {TEMP_OUTPUT_FILE}")
except OSError as e: print_color(f"Warning: Could not remove {TEMP_OUTPUT_FILE}: {e}", COLOR_YELLOW)
if os.path.exists(EXECUTABLE):
try:
os.remove(EXECUTABLE)
print(f"Removed executable: {EXECUTABLE}")
except OSError as e: print_color(f"Warning: Could not remove {EXECUTABLE}: {e}", COLOR_YELLOW)
# 5. Print Summary (Kept identical to original)
print_color("\n--- Test Summary ---", COLOR_BLUE)
print_color(f"Passed: {results['passed']}", COLOR_GREEN)
print_color(f"Failed: {results['failed']}", COLOR_RED if results['failed'] > 0 else COLOR_GREEN)
print_color(f"Skipped: {results['skipped']}", COLOR_YELLOW if results['skipped'] > 0 else COLOR_GREEN)
total_run = results['passed'] + results['failed']
total_defined = total_run + results['skipped']
print(f"Total Tests Defined: {total_defined}")
print(f"Total Tests Run: {total_run}")
print(f"Total Test Suite Execution Time: {total_test_suite_duration:.3f}s")
# Performance Summary (Kept identical)
if test_durations:
total_passed_time = sum(test_durations)
avg_time = total_passed_time / len(test_durations)
max_time = max(test_durations)
min_time = min(test_durations)
print("\n--- Performance Summary (Passed Tests) ---")
print(f"Total execution time (passed tests): {total_passed_time:.3f}s")
print(f"Average execution time per test: {avg_time:.3f}s")
print(f"Fastest test execution time: {min_time:.3f}s")
print(f"Slowest test execution time: {max_time:.3f}s")
# Memory Summary (Kept identical)
if MEASURE_MEMORY: # Check final flag state
if test_memory_usages:
total_mem_kb = sum(test_memory_usages)
avg_mem_kb = total_mem_kb / len(test_memory_usages)
max_mem_kb = max(test_memory_usages)
min_mem_kb = min(test_memory_usages)
total_mem_mb = total_mem_kb / 1024
total_mem_gb = total_mem_mb / 1024
if total_mem_gb > 1: total_mem_str = f"{total_mem_gb:.2f} GB"
elif total_mem_mb > 1: total_mem_str = f"{total_mem_mb:.2f} MB"
else: total_mem_str = f"{total_mem_kb} KB"
print("\n--- Memory Usage Summary (Passed Tests) ---")
print(f"Cumulative peak memory (passed tests): {total_mem_str} ({total_mem_kb} KB)")
print(f"Average peak memory per test: {avg_mem_kb:.1f} KB")
print(f"Lowest peak memory usage: {min_mem_kb} KB")
print(f"Highest peak memory usage: {max_mem_kb} KB")
else:
print("\n--- Memory Usage Summary (Passed Tests) ---")
print("(No memory usage data collected for passed tests - check warnings)")
# Final Result (Kept identical logic)
if failed_tests: # This list now includes skipped tests as well
# Determine overall status based on presence of actual failures
has_failures = any("SKIPPED" not in test for test in failed_tests if results['failed'] > 0) # Check if there are non-skip failures
if has_failures:
print_color("\n--- Failed/Skipped Test Cases ---", COLOR_RED)
for test in failed_tests:
if "SKIPPED" in test:
print_color(f" - {test}", COLOR_YELLOW)
else:
print_color(f" - {test}", COLOR_RED)
print_color("\nTest suite finished with failures.", COLOR_RED)
sys.exit(1) # Exit with error code if any test *failed*
elif results['skipped'] > 0 : # Only skips, no failures
print_color("\n--- Skipped Test Cases ---", COLOR_YELLOW)
for test in failed_tests:
print(f" - {test}")
if results['passed'] > 0:
print_color("\nAll executed tests passed successfully, but some were skipped.", COLOR_GREEN)
else:
print_color("\nWarning: No tests were executed successfully (all skipped or none defined).", COLOR_YELLOW)
sys.exit(0) # Exit normally even if skips occurred
elif results['passed'] == 0 and results['skipped'] == total_defined and total_defined > 0:
print_color("\nWarning: No tests were executed (all skipped).", COLOR_YELLOW)
sys.exit(0)
elif results['passed'] > 0 :
print_color("\nAll executed tests passed successfully!", COLOR_GREEN)
sys.exit(0)
else: # Should only happen if test_cases is empty
print_color("\nNo tests were defined or executed.", COLOR_YELLOW)
sys.exit(0)

View File

@@ -0,0 +1,4 @@
#pragma once
const int TOP_K_CANDIDATES = 3;
const int TOP_N_OUTPUT = 20;

View File

@@ -0,0 +1,34 @@
#pragma once
#include <string>
#include "TopKVideoHolder.h"
struct HashtagInfo {
const std::string* name = nullptr;
long totalViews = 0;
int usageCount = 0;
TopKVideoHolder topVideos;
HashtagInfo() = default;
explicit HashtagInfo(const std::string* n) : name(n), totalViews(0), usageCount(0) {}
};
struct CompareHashtagPtr {
bool operator()(const HashtagInfo* a, const HashtagInfo* b) const {
if (a->usageCount != b->usageCount) return a->usageCount > b->usageCount;
if (a->totalViews != b->totalViews) return a->totalViews > b->totalViews;
if (a->name && b->name) return *a->name < *b->name;
if (a->name) return true;
return false;
}
};
struct CompareHashtagPtrForHeap {
bool operator()(const HashtagInfo* a, const HashtagInfo* b) const {
if (a->usageCount != b->usageCount) return a->usageCount > b->usageCount;
if (a->totalViews != b->totalViews) return a->totalViews > b->totalViews;
if (a->name && b->name) return *a->name > *b->name;
if (a->name) return false;
return true;
}
};

View File

@@ -191,7 +191,7 @@ your program should produce an output similar to what TikTok does (of course we
this basically is the trending hashtags, each is associated with some videos. In your output, these videos should be sorted in a descending order, based on how many views the video has received.
More specifically, you should print the top 20 trending hashtags, and then for each hashtag, print 3 videos which use this hashtag in its post text. If a hashtag is used in 100 videos, select the 3 (out of these 100) most viewed videos. Print the most viewed video first, and then print the next most viewed video, and then the third most viewed video. In the case where two videos both use this hashtag have the same view count, pick the one which appears first in the input json file. However, if both needs to be printed, meaning that both are in the top 3, then check the video id (as an std::string object); if a.videoId > b.videoId, then display video a first.
More specifically, you should print the top 20 trending hashtags, and then for each hashtag, print 3 videos which use this hashtag in its post text. If a hashtag is used in 100 videos, select the 3 (out of these 100) most viewed videos. Print the most viewed video first, and then print the next most viewed video, and then the third most viewed video. In the case where two videos both use this hashtag have the same view count, then break the tie by comparing the video id (as an std::string object); if a.videoId > b.videoId, then display video a first.
Definition of the top 20 trending hashtags: this should be the based on the usage of the hashtag - how many times in total each hashtag is used. When two hashtags are both used for the same amount of times, break the tie by the total view count of the videos associated with each hashtag. And if still a tie, break the tie by comparing the hashtag names, i.e., apply the less than operator (<) to the two names - both are std::strings, the hashtag whose name is less than the name of the other hashtag should be the winner and should be displayed first.
@@ -225,7 +225,7 @@ your program should produce an output similar to what TikTok does (of course we
this basically is the trending sounds, each is associated with some videos. In your output, these videos should be sorted in a descending order, based on how many views the video has received.
More specifically, you should print the top 20 trending sounds, and then for each sound, print 3 videos which use this sound. If a sound is used in 100 videos, select the 3 (out of these 100) most viewed videos. Print the most viewed video first, and then print the next most viewed video, and then the third most viewed video. In the case where two videos both use this sound have the same view count, pick the one which appears first in the input json file. However, if both needs to be printed, meaning that both are in the top 3, then check the video id (as an std::string object); if a.videoId > b.videoId, then display video a first.
More specifically, you should print the top 20 trending sounds, and then for each sound, print 3 videos which use this sound. If a sound is used in 100 videos, select the 3 (out of these 100) most viewed videos. Print the most viewed video first, and then print the next most viewed video, and then the third most viewed video. In the case where two videos both use this sound have the same view count, then break the tie by comparing the video id (as an std::string object); if a.videoId > b.videoId, then display video a first.
Definition of the top 20 trending sounds: this should be the based on the total view count of the videos which use this sound. If there is a tie, break the tie by comparing the music id, i.e., apply the less than operator (<) to the two music ids - both are std::strings, the sound whose music id is less than the music id of the other sound should be the winner, and should be displayed first.
@@ -297,9 +297,9 @@ You can test (but not view) the instructor's code here: [instructor code](http:/
## FAQs
q1: For cases where a hashtag appears twice on a line does that count as two separate URLs?
q1: Some videos appear multiple times in the same json file, how shall we handle such cases?
a1: Yes, a hashtag appears 2 times in one post will be counted as 2 times. This is the reason why some URLs appear twice in the output as the top 3 videos of some hashtag - as in that #foryou case in tiny1. (This is just to simplify your implementation).
a1: If a video appears multiple times in the same json file, only the first occurrence should be considered, and this means that when parsing the json file, and if you find a duplicated video ID, then that line which contains the duplicated video ID should not be parsed at all.
q2: What bonus do I get if my program ranks higher than the instructor's program on the leaderboard?
@@ -320,13 +320,13 @@ a4: 60. Submitty will deduct points once you submit more than 60 times.
## Rubric
17 pts
15 pts
- README.txt Completed (3 pts)
- One of name, collaborators, or hours not filled in. (-1)
- Two or more of name, collaborators, or hours not filled in. (-2)
- No reflection. (-1)
- IMPLEMENTATION AND CODING STYLE (8 pts)
- No credit (significantly incomplete implementation) (-8)
- IMPLEMENTATION AND CODING STYLE (6 pts)
- No credit (significantly incomplete implementation) (-6)
- Putting almost everything in the main function. It's better to create separate functions for different tasks. (-2)
- Function bodies containing more than one statement are placed in the .h file. (okay for templated classes) (-2)
- Functions are not well documented or are poorly commented, in either the .h or the .cpp file. (-1)
@@ -338,6 +338,7 @@ a4: 60. Submitty will deduct points once you submit more than 60 times.
- DATA REPRESENTATION (6 pts)
- No credit (significantly incomplete implementation). (-6)
- Does not use std::priority_queue at all. (-6)
- Uses std::priority_queue but does not use it in a meaningful way. (-5)
<!--
- Member variables are public. (-2)
- Exceptions (2 pts)

View File

@@ -1,7 +1,7 @@
HOMEWORK 10: Tiktok Trends
HOMEWORK 9: Tiktok Trends
NAME: < insert name >
NAME: Jinshan Zhou
COLLABORATORS AND OTHER RESOURCES:
@@ -10,17 +10,21 @@ List the names of everyone you talked to about this assignment
LMS, etc.), and all of the resources (books, online reference
material, etc.) you consulted in completing this assignment.
< insert collaborators / resources >
A lot, like using Top K for better sorting. Difference IO cases (not useful).
StringCache (not useful). shared_ptr (not really help) and may websites, cases
that I don't remember anymore.
Remember: Your implementation for this assignment must be done on your
own, as described in "Academic Integrity for Homework" handout.
ESTIMATE OF # OF HOURS SPENT ON THIS ASSIGNMENT: < insert # hours >
ESTIMATE OF # OF HOURS SPENT ON THIS ASSIGNMENT: over 20 hr, 8 hr for complete
more than 10 hr just for optimization.
MISC. COMMENTS TO GRADER:
(optional, please be concise!)
The program is a bit messy. Since, I tried too many techniques. Some are broken
changes, so I have to add patch to it. Myself is also a bit lose to my code.
## Reflection and Self Assessment
@@ -32,5 +36,11 @@ What parts of the assignment did you find challenging? Is there anything that
finally "clicked" for you in the process of working on this assignment? How well
did the development and testing process go for you?
< insert reflection >
This was definitely the most challenging assignment I've ever seen, and the
hard part was identifying performance issues and optimizing them. It was not
easy, I used various tools like perf and found that the JSON part had the biggest
overhead. Optimizing it showed immediate results, but then I hit a bottleneck,
so I started using various schemes, and most of them didn't work. Then I had to
push back and design the business process from scratch and finally got inspired
by my professor to use unordered set to get the best performance
(last 0.1 seconds).

View File

@@ -0,0 +1,35 @@
#pragma once
#include <string>
#include "TopKVideoHolder.h"
struct SoundInfo {
const std::string* musicId = nullptr;
const std::string* musicName = nullptr;
const std::string* musicAuthor = nullptr;
long totalViews = 0;
TopKVideoHolder topVideos;
SoundInfo() = default;
SoundInfo(const std::string* id, const std::string* name, const std::string* author)
: musicId(id), musicName(name), musicAuthor(author), totalViews(0) {}
};
struct CompareSoundPtr {
bool operator()(const SoundInfo* a, const SoundInfo* b) const {
if (a->totalViews != b->totalViews) return a->totalViews > b->totalViews;
if (a->musicId && b->musicId) return *a->musicId < *b->musicId;
if (a->musicId) return true;
return false;
}
};
struct CompareSoundPtrForHeap {
bool operator()(const SoundInfo* a, const SoundInfo* b) const {
if (a->totalViews != b->totalViews) return a->totalViews > b->totalViews;
if (a->musicId && b->musicId) return *a->musicId > *b->musicId;
if (a->musicId) return false;
return true;
}
};

View File

@@ -0,0 +1,15 @@
#include "StringInterner.h"
const std::string* StringInterner::intern(const std::string& str) {
std::pair<std::unordered_set<std::string>::iterator, bool> result = pool.insert(str);
return &(*result.first);
}
const std::string* StringInterner::intern(std::string&& str) {
std::pair<std::unordered_set<std::string>::iterator, bool> result = pool.insert(std::move(str));
return &(*result.first);
}
const std::string* StringInterner::getEmptyString() {
return intern("");
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <string>
#include <unordered_set>
#include <utility>
class StringInterner {
private:
std::unordered_set<std::string> pool;
public:
const std::string* intern(const std::string& str);
const std::string* intern(std::string&& str);
const std::string* getEmptyString();
};

View File

@@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <functional>
struct StringPtrHash {
size_t operator()(const std::string* s) const {
return std::hash<std::string>()(*s);
}
};
struct StringPtrEqual {
bool operator()(const std::string* a, const std::string* b) const {
if (a == b) return true;
if (!a || !b) return false;
return *a == *b;
}
};

View File

@@ -0,0 +1,32 @@
#include "TopKVideoHolder.h"
void TopKVideoHolder::add(const VideoInfo& video) {
if (pq.size() < K) {
pq.push(video);
} else {
if (VideoCompareWorse()(video, pq.top())) {
pq.pop();
pq.push(video);
}
}
}
std::vector<VideoInfo> TopKVideoHolder::getSortedVideos() {
std::vector<VideoInfo> sortedVideos;
size_t current_size = pq.size();
if (current_size == 0) return sortedVideos;
sortedVideos.reserve(current_size);
while (!pq.empty()) {
sortedVideos.push_back(pq.top());
pq.pop();
}
std::sort(sortedVideos.begin(), sortedVideos.end(), VideoInfo::compareForFinalSort);
return sortedVideos;
}
bool TopKVideoHolder::empty() const { return pq.empty(); }
size_t TopKVideoHolder::size() const { return pq.size(); }

View File

@@ -0,0 +1,19 @@
#pragma once
#include <vector>
#include <queue>
#include <algorithm>
#include "VideoInfo.h"
#include "Constants.h"
class TopKVideoHolder {
private:
std::priority_queue<VideoInfo, std::vector<VideoInfo>, VideoCompareWorse> pq;
static const size_t K = TOP_K_CANDIDATES;
public:
void add(const VideoInfo& video);
std::vector<VideoInfo> getSortedVideos();
bool empty() const;
size_t size() const;
};

265
hws/tiktok_trends/Utils.cpp Normal file
View File

@@ -0,0 +1,265 @@
#include "Utils.h"
#include <iostream> // For potential cerr usage, although not directly in these functions
#include <cctype>
#include <cstring>
#include <algorithm> // For std::min
bool parseQuotedStringValue(const std::string& str, size_t& pos, std::string& value) {
const size_t strLen = str.length();
value.clear();
if (pos >= strLen || str[pos] != '"') return false;
++pos;
const size_t startPos = pos;
const char* strData = str.data();
while (pos < strLen && strData[pos] != '"') {
++pos;
}
if (pos >= strLen) return false;
value.assign(strData + startPos, pos - startPos);
++pos;
return true;
}
bool parseUnquotedValue(const std::string& str, size_t& pos, std::string& value) {
const size_t strLen = str.length();
value.clear();
const size_t startPos = pos;
const char* strData = str.data();
while (pos < strLen && strData[pos] != ',' && strData[pos] != '}' && strData[pos] != ']' && !std::isspace(static_cast<unsigned char>(strData[pos]))) {
++pos;
}
if (startPos == pos) return false;
value.assign(strData + startPos, pos - startPos);
return true;
}
bool extractValue(const std::string& line, const std::string& key, std::string& value) {
const std::string searchKey = "\"" + key + "\":";
const char* found_pos = strstr(line.c_str(), searchKey.c_str());
if (!found_pos) return false;
size_t pos = (found_pos - line.c_str()) + searchKey.length();
const size_t lineLen = line.length();
while (pos < lineLen && std::isspace(static_cast<unsigned char>(line[pos]))) {
++pos;
}
if (pos >= lineLen) return false;
if (line[pos] == '"') {
return parseQuotedStringValue(line, pos, value);
} else {
return parseUnquotedValue(line, pos, value);
}
}
bool extractSubObject(const std::string& line, const std::string& key, std::string& subObj) {
const std::string searchKey = "\"" + key + "\":";
const char* found_pos = strstr(line.c_str(), searchKey.c_str());
if (!found_pos) return false;
size_t pos = (found_pos - line.c_str()) + searchKey.length();
const size_t lineLen = line.length();
while (pos < lineLen && std::isspace(static_cast<unsigned char>(line[pos]))) ++pos;
if (pos >= lineLen || line[pos] != '{') return false;
const size_t startBracePos = pos;
int braceCount = 1;
++pos;
const char* lineData = line.data();
bool inString = false;
char prevChar = 0;
while (pos < lineLen && braceCount > 0) {
const char c = lineData[pos];
if (c == '"' && prevChar != '\\') {
inString = !inString;
} else if (!inString) {
if (c == '{') {
++braceCount;
} else if (c == '}') {
--braceCount;
}
}
prevChar = (prevChar == '\\' && c == '\\') ? 0 : c;
++pos;
}
if (braceCount == 0) {
subObj.assign(lineData + startBracePos, pos - startBracePos);
return true;
}
return false;
}
bool parseLongLong(const std::string& s, long& result) {
result = 0;
if (s.empty()) return false;
const char* ptr = s.c_str();
bool negative = false;
long current_val = 0;
if (*ptr == '-') {
negative = true;
++ptr;
}
if (!*ptr) return false;
while (*ptr) {
if (*ptr >= '0' && *ptr <= '9') {
long digit = (*ptr - '0');
current_val = current_val * 10 + digit;
} else {
return false;
}
++ptr;
}
result = negative ? -current_val : current_val;
return true;
}
bool parseLineForHashtags(const std::string& line, int inputOrder, StringInterner& interner,
VideoInfo& outVideo, std::string& outText)
{
outText.clear();
std::string id_str, coverUrl_str, webVideoUrl_str, playCount_str;
if (!extractValue(line, "id", id_str) || id_str.empty()) return false;
long playCount = 0;
if (extractValue(line, "playCount", playCount_str)) {
parseLongLong(playCount_str, playCount);
}
extractValue(line, "text", outText);
extractValue(line, "webVideoUrl", webVideoUrl_str);
std::string videoMetaSub;
if (extractSubObject(line, "videoMeta", videoMetaSub)) {
extractValue(videoMetaSub, "coverUrl", coverUrl_str);
}
outVideo = VideoInfo(
interner.intern(std::move(id_str)),
interner.intern(std::move(coverUrl_str)),
interner.intern(std::move(webVideoUrl_str)),
playCount,
inputOrder
);
return true;
}
bool parseLineForSounds(const std::string& line, int inputOrder, StringInterner& interner,
VideoInfo& outVideo,
const std::string*& outMusicIdPtr,
const std::string*& outMusicNamePtr,
const std::string*& outMusicAuthorPtr)
{
std::string id_str, coverUrl_str, webVideoUrl_str, playCount_str;
std::string musicId_str, musicName_str, musicAuthor_str;
if (!extractValue(line, "id", id_str) || id_str.empty()) return false;
long playCount = 0;
if (extractValue(line, "playCount", playCount_str)) {
parseLongLong(playCount_str, playCount);
}
std::string musicMetaSub;
if (extractSubObject(line, "musicMeta", musicMetaSub)) {
extractValue(musicMetaSub, "musicId", musicId_str);
extractValue(musicMetaSub, "musicName", musicName_str);
extractValue(musicMetaSub, "musicAuthor", musicAuthor_str);
}
if (musicId_str.empty()) {
return false;
}
extractValue(line, "webVideoUrl", webVideoUrl_str);
std::string videoMetaSub;
if (extractSubObject(line, "videoMeta", videoMetaSub)) {
extractValue(videoMetaSub, "coverUrl", coverUrl_str);
}
outVideo = VideoInfo(
interner.intern(std::move(id_str)),
interner.intern(std::move(coverUrl_str)),
interner.intern(std::move(webVideoUrl_str)),
playCount,
inputOrder
);
outMusicIdPtr = interner.intern(std::move(musicId_str));
outMusicNamePtr = interner.intern(std::move(musicName_str));
outMusicAuthorPtr = interner.intern(std::move(musicAuthor_str));
return true;
}
void extractHashtags(const std::string& text,
std::unordered_map<const std::string*, HashtagInfo, StringPtrHash, StringPtrEqual>& hashtagData,
StringInterner& interner,
const VideoInfo& video)
{
const size_t textLen = text.length();
const char* textData = text.data();
size_t pos = 0;
std::string tag_buffer;
tag_buffer.reserve(50);
while (pos < textLen) {
while (pos < textLen && textData[pos] != '#') {
pos++;
}
if (pos >= textLen) break;
size_t start = pos + 1;
if (start >= textLen) break;
size_t end = start;
while (end < textLen && (std::isalnum(static_cast<unsigned char>(textData[end])) || textData[end] == '_')) {
end++;
}
if (end > start) {
tag_buffer.assign(textData + start, end - start);
const std::string* hashtagPtr = interner.intern(tag_buffer);
typedef std::unordered_map<const std::string*, HashtagInfo, StringPtrHash, StringPtrEqual> HashtagMapType;
HashtagMapType::iterator it = hashtagData.find(hashtagPtr);
if (it == hashtagData.end()) {
std::pair<HashtagMapType::iterator, bool> emplace_result =
hashtagData.emplace(hashtagPtr, HashtagInfo(hashtagPtr));
it = emplace_result.first;
}
it->second.usageCount++;
it->second.totalViews += video.playCount;
it->second.topVideos.add(video);
}
pos = end;
}
}
void extractSortAndPrintTop3Videos(std::ofstream& fout, TopKVideoHolder& topVideos) {
std::vector<VideoInfo> sortedTopVideos = topVideos.getSortedVideos();
int videosToPrint = std::min(static_cast<int>(sortedTopVideos.size()), TOP_K_CANDIDATES);
for (int i = 0; i < videosToPrint; ++i) {
const VideoInfo& video = sortedTopVideos[i];
fout << "cover url: " << (video.coverUrl && !video.coverUrl->empty() ? *video.coverUrl : "null") << "\n";
fout << "web video url: " << (video.webVideoUrl && !video.webVideoUrl->empty() ? *video.webVideoUrl : "null") << "\n";
}
}

33
hws/tiktok_trends/Utils.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include "StringInterner.h"
#include "VideoInfo.h"
#include "HashtagInfo.h"
#include "SoundInfo.h"
#include "StringPtrUtils.h" // Needed for HashtagMapType/SoundMapType in function signatures
bool parseQuotedStringValue(const std::string& str, size_t& pos, std::string& value);
bool parseUnquotedValue(const std::string& str, size_t& pos, std::string& value);
bool extractValue(const std::string& line, const std::string& key, std::string& value);
bool extractSubObject(const std::string& line, const std::string& key, std::string& subObj);
bool parseLongLong(const std::string& s, long& result);
bool parseLineForHashtags(const std::string& line, int inputOrder, StringInterner& interner,
VideoInfo& outVideo, std::string& outText);
bool parseLineForSounds(const std::string& line, int inputOrder, StringInterner& interner,
VideoInfo& outVideo,
const std::string*& outMusicIdPtr,
const std::string*& outMusicNamePtr,
const std::string*& outMusicAuthorPtr);
void extractHashtags(const std::string& text,
std::unordered_map<const std::string*, HashtagInfo, StringPtrHash, StringPtrEqual>& hashtagData,
StringInterner& interner,
const VideoInfo& video);
void extractSortAndPrintTop3Videos(std::ofstream& fout, TopKVideoHolder& topVideos);

View File

@@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <algorithm>
#include "Constants.h"
struct VideoInfo {
const std::string* videoId = nullptr;
const std::string* coverUrl = nullptr;
const std::string* webVideoUrl = nullptr;
long playCount = 0;
int inputOrder = -1;
VideoInfo() = default;
VideoInfo(const std::string* id, const std::string* cover, const std::string* web,
long plays, int order)
: videoId(id), coverUrl(cover), webVideoUrl(web), playCount(plays), inputOrder(order) {}
static bool compareForFinalSort(const VideoInfo& a, const VideoInfo& b) {
if (a.playCount != b.playCount) return a.playCount > b.playCount;
if (a.videoId && b.videoId && *a.videoId != *b.videoId) return *a.videoId < *b.videoId;
return a.inputOrder < b.inputOrder;
}
bool operator<(const VideoInfo& other) const {
if (playCount != other.playCount) return playCount > other.playCount;
return inputOrder < other.inputOrder;
}
};
struct VideoCompareWorse {
bool operator()(const VideoInfo& a, const VideoInfo& b) const {
if (a.playCount != b.playCount) return a.playCount > b.playCount;
return a.inputOrder < b.inputOrder;
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More