Compare commits
105 Commits
948973fee7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab5cc82410 | ||
|
|
73be805725 | ||
|
|
78ead22162 | ||
|
|
ab968cd2cc | ||
|
|
7ceb05ce98 | ||
|
|
4903366103 | ||
|
|
69ecea3dd9 | ||
|
|
8edd8a5221 | ||
|
|
1ffe82c107 | ||
|
|
693b853865 | ||
|
|
09273e9338 | ||
|
|
506ab00200 | ||
|
|
cc21696e46 | ||
|
|
fea90f2b4d | ||
|
|
50bbf8c2a7 | ||
|
|
c3588e8541 | ||
|
|
643c7f1038 | ||
|
|
083ae76743 | ||
|
|
efc2fbaaa3 | ||
|
|
2d3840b312 | ||
|
|
6a17be892f | ||
|
|
d6ef9dff46 | ||
|
|
3fd45d0b8c | ||
|
|
421ae870a4 | ||
|
|
1383572b2f | ||
|
|
2fa18a094f | ||
|
|
83e54834d8 | ||
|
|
28243a596d | ||
|
|
3c88acac9f | ||
|
|
97fd05d08c | ||
|
|
f187301c4a | ||
|
|
4060e32a9a | ||
|
|
f320060f7b | ||
|
|
d0385b27f1 | ||
|
|
7b9d3983ed | ||
|
|
c24a7592ab | ||
|
|
d4cd201b05 | ||
|
|
415b60e129 | ||
|
|
64aa4ba11c | ||
|
|
feeb84eae7 | ||
|
|
0fc129f674 | ||
|
|
284bd60029 | ||
|
|
45124db960 | ||
|
|
2e224420e9 | ||
|
|
b8fde0d18e | ||
|
|
2f84e7ed34 | ||
|
|
329699721f | ||
|
|
30fa11776d | ||
|
|
418411d62d | ||
|
|
fd6ef6545e | ||
|
|
a109046498 | ||
|
|
9ec5d3d32c | ||
|
|
727b4fccd0 | ||
|
|
6670134276 | ||
|
|
7eab384626 | ||
|
|
728024087b | ||
|
|
81a4824b0a | ||
|
|
999bf68f77 | ||
|
|
06c33e577b | ||
|
|
34d5900d2c | ||
|
|
f858d57e31 | ||
|
|
8102db4dac | ||
|
|
63ddc1cc22 | ||
|
|
12e95dedfc | ||
|
|
114bc00e8b | ||
|
|
33cf2cbeaa | ||
|
|
e2c9cf9566 | ||
|
|
3514476080 | ||
|
|
58b8a67d55 | ||
|
|
456cdb961a | ||
|
|
628104fa39 | ||
|
|
877576afc5 | ||
|
|
03021491c5 | ||
|
|
5f34a80e42 | ||
|
|
73b0ece069 | ||
|
|
53549accd0 | ||
|
|
7682f722f1 | ||
|
|
3cf7288c34 | ||
|
|
302a2f176d | ||
|
|
bb38a699a0 | ||
|
|
bfc95ce254 | ||
|
|
89609d0937 | ||
|
|
107a578154 | ||
|
|
1c454d2ecd | ||
|
|
425b39cf7f | ||
|
|
8e49abd24a | ||
|
|
d8190eeea8 | ||
|
|
235168710e | ||
|
|
28a8f7d285 | ||
|
|
110f6cc319 | ||
|
|
7b9c74b098 | ||
|
|
34f972e3f4 | ||
|
|
7f7b936341 | ||
|
|
21fa2c03de | ||
|
|
9096194d90 | ||
|
|
a46c3caa59 | ||
|
|
1311832955 | ||
|
|
0f6391d57a | ||
|
|
ae3b122fb1 | ||
| bb359c9fe7 | |||
|
|
e41e5e4a0c | ||
|
|
6bdc38ef77 | ||
|
|
53824b1147 | ||
|
|
1ade7ffdde | ||
|
|
47cbeb649f |
34
.vscode/launch.json
vendored
@@ -135,6 +135,40 @@
|
|||||||
"MIMode": "gdb",
|
"MIMode": "gdb",
|
||||||
"miDebuggerPath": "/usr/bin/gdb",
|
"miDebuggerPath": "/usr/bin/gdb",
|
||||||
"preLaunchTask": "C/C++: g++ build active file"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
7
.vscode/settings.json
vendored
@@ -78,6 +78,11 @@
|
|||||||
"unordered_set": "cpp",
|
"unordered_set": "cpp",
|
||||||
"regex": "cpp",
|
"regex": "cpp",
|
||||||
"cinttypes": "cpp",
|
"cinttypes": "cpp",
|
||||||
"__node_handle": "cpp"
|
"__node_handle": "cpp",
|
||||||
|
"shared_mutex": "cpp",
|
||||||
|
"cfenv": "cpp",
|
||||||
|
"locale": "cpp",
|
||||||
|
"filesystem": "cpp",
|
||||||
|
"__split_buffer": "cpp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
18
animations/hash_tables/index.html
Normal 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>
|
||||||
734
animations/hash_tables/main.js
Normal 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();
|
||||||
|
}
|
||||||
504
animations/heap/sort/heap-sort.js
Normal 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 += ' ';
|
||||||
|
} 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;
|
||||||
142
animations/heap/sort/index.html
Normal 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>
|
||||||
72
animations/maps/example1.html
Normal 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
@@ -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><<span style="color:blue">int</span>>& 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><<span style="color:blue">int</span>,<span style="color:blue">int</span>> map1;',
|
||||||
|
'<span class="line-number">4.</span> <span style="color:blue">for</span>(<span style="color:blue">int</span> i=0; i<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]] <= 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
@@ -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;
|
||||||
|
}
|
||||||
32
animations/priority_queue/max_heap/index.html
Normal 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>
|
||||||
18926
animations/priority_queue/max_heap/konva.js
Normal file
288
animations/priority_queue/max_heap/max_heap.js
Normal 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();
|
||||||
32
animations/priority_queue/min_heap/index.html
Normal 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>
|
||||||
18926
animations/priority_queue/min_heap/konva.js
Normal file
290
animations/priority_queue/min_heap/min_heap.js
Normal 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();
|
||||||
55
animations/trees/iterative/inorder/inorder.css
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
30
animations/trees/iterative/inorder/inorder.html
Normal 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>
|
||||||
266
animations/trees/iterative/inorder/inorder.js
Normal 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();
|
||||||
18223
animations/trees/iterative/inorder/konva.js
Normal file
18223
animations/trees/iterative/postorder/konva.js
Normal file
54
animations/trees/iterative/postorder/postorder.css
Normal 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;
|
||||||
|
}
|
||||||
30
animations/trees/iterative/postorder/postorder.html
Normal 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>
|
||||||
283
animations/trees/iterative/postorder/postorder.js
Normal 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();
|
||||||
18223
animations/trees/iterative/preorder/konva.js
Normal file
54
animations/trees/iterative/preorder/preorder.css
Normal 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;
|
||||||
|
}
|
||||||
30
animations/trees/iterative/preorder/preorder.html
Normal 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>
|
||||||
278
animations/trees/iterative/preorder/preorder.js
Normal 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();
|
||||||
146
animations/trees/iterative/traversal.css
Normal 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;
|
||||||
|
}
|
||||||
31
animations/trees/iterative/traversal.html
Normal 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>
|
||||||
251
animations/trees/iterative/traversal.js
Normal 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();
|
||||||
61
animations/trees/morris/morrisPostOrder.html
Normal 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>
|
||||||
1327
animations/trees/morris/morris_pre_order.html
Normal file
623
animations/trees/morris/script.js
Normal 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 Post‑Order 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);
|
||||||
|
};
|
||||||
166
animations/trees/morris/style.css
Normal 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;
|
||||||
|
}
|
||||||
68
hws/instagram_notifications/Notification.h
Normal 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
|
||||||
@@ -5,9 +5,6 @@ In this assignment you will develop a program to deliver notifications to users
|
|||||||
## Learning Objectives
|
## Learning Objectives
|
||||||
|
|
||||||
- Practice using C++ inheritance and polymorphism.
|
- Practice using C++ inheritance and polymorphism.
|
||||||
- Practice using C++ exceptions.
|
|
||||||
- Practice using std::queue.
|
|
||||||
- Practice using std::stack.
|
|
||||||
|
|
||||||
## Background
|
## 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:
|
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:\
|
||||||

|

|
||||||
|
|
||||||
To turn on or off tag notifications:
|
To turn on or off tag notifications:\
|
||||||

|

|
||||||
|
|
||||||
To turn on or off comment notifications:
|
To turn on or off comment notifications:\
|
||||||

|

|
||||||
|
|
||||||
To turn on or off follow notifications:
|
To turn on or off follow notifications:\
|
||||||

|

|
||||||
|
|
||||||
To turn on or off message request notifications:
|
To turn on or off message request notifications:\
|
||||||

|

|
||||||
|
|
||||||
Users can also decide to pause all notifications:
|
Users can also decide to pause all notifications:\
|
||||||

|

|
||||||
|
|
||||||
## Supported Commands
|
## 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
|
## 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: don’t 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 don’t forget to comment your code! Use the provided template [README.txt](./README.txt) file for notes you want the grader to read.
|
<!--**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.-->
|
||||||
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.
|
|
||||||
|
|
||||||
**Due Date**: 12/07/2023, Thursday, 23:59pm.
|
Use good coding style when you design and implement your program. Organize your program into functions: don’t 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 don’t 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
|
## 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)
|
- No credit (significantly incomplete implementation) (-6)
|
||||||
- Putting almost everything in the main function. It's better to create separate functions for different tasks. (-2)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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 file organization: Puts more than one class in a file (okay for very small helper classes). (-1)
|
||||||
- Poor variable names. (-1)
|
- Poor choice of variable names: non-descriptive names (e.g. 'vec', 'str', 'var'), single-letter variable names (except single loop counter), etc. (-1)
|
||||||
- Uses global variables. (-1)
|
- DATA REPRESENTATION (8 pts)
|
||||||
- Contains useless comments like commented-out code, terminal commands, or silly notes. (-1)
|
- Does not define the Notification base class. (-8)
|
||||||
- DATA REPRESENTATION (6 pts)
|
- Does not define any of the five derived Notification classes. (-8)
|
||||||
- Does not define the Notification base class. (-6)
|
- One of the five derived Notification classes is missing. (-2)
|
||||||
- Does not define any of the five derived Notification classes. (-6)
|
- Two of the five derived Notification classes are missing. (-4)
|
||||||
- One of the five derived Notification classes is missing. (-1)
|
|
||||||
- Two of the five derived Notification classes are missing. (-2)
|
|
||||||
- Three or more of the five derived Notification classes are missing. (-6)
|
- Three or more of the five derived Notification classes are missing. (-6)
|
||||||
- Member variables are public. (-2)
|
<!-- - Member variables are public. (-2)
|
||||||
- Exceptions (2 pts)
|
-- - Exceptions (2 pts)
|
||||||
- Does not use try/throw/catch anywhere in the code. (-2)
|
- Does not use try/throw/catch anywhere in the code. (-2)-->
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
HOMEWORK 10: Instagram Notifications
|
HOMEWORK 10: Instagram Notifications
|
||||||
|
|
||||||
|
|
||||||
NAME: < insert name >
|
NAME: Jinshan Zhou
|
||||||
|
|
||||||
|
|
||||||
COLLABORATORS AND OTHER RESOURCES:
|
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
|
LMS, etc.), and all of the resources (books, online reference
|
||||||
material, etc.) you consulted in completing this assignment.
|
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
|
Remember: Your implementation for this assignment must be done on your
|
||||||
own, as described in "Academic Integrity for Homework" handout.
|
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:
|
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
|
finally "clicked" for you in the process of working on this assignment? How well
|
||||||
did the development and testing process go for you?
|
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.
|
||||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 770 KiB After Width: | Height: | Size: 770 KiB |
197
hws/instagram_notifications/main.cpp
Normal 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 on‑the‑fly 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;
|
||||||
|
}
|
||||||
517
hws/instagram_notifications/test.py
Normal 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)
|
||||||
4
hws/tiktok_trends/Constants.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
const int TOP_K_CANDIDATES = 3;
|
||||||
|
const int TOP_N_OUTPUT = 20;
|
||||||
34
hws/tiktok_trends/HashtagInfo.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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.
|
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.
|
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.
|
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.
|
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
|
## 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?
|
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
|
## Rubric
|
||||||
|
|
||||||
17 pts
|
15 pts
|
||||||
- README.txt Completed (3 pts)
|
- README.txt Completed (3 pts)
|
||||||
- One of name, collaborators, or hours not filled in. (-1)
|
- One of name, collaborators, or hours not filled in. (-1)
|
||||||
- Two or more of name, collaborators, or hours not filled in. (-2)
|
- Two or more of name, collaborators, or hours not filled in. (-2)
|
||||||
- No reflection. (-1)
|
- No reflection. (-1)
|
||||||
- IMPLEMENTATION AND CODING STYLE (8 pts)
|
- IMPLEMENTATION AND CODING STYLE (6 pts)
|
||||||
- No credit (significantly incomplete implementation) (-8)
|
- No credit (significantly incomplete implementation) (-6)
|
||||||
- Putting almost everything in the main function. It's better to create separate functions for different tasks. (-2)
|
- 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)
|
- 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)
|
- 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)
|
- DATA REPRESENTATION (6 pts)
|
||||||
- No credit (significantly incomplete implementation). (-6)
|
- No credit (significantly incomplete implementation). (-6)
|
||||||
- Does not use std::priority_queue at all. (-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)
|
- Member variables are public. (-2)
|
||||||
- Exceptions (2 pts)
|
- Exceptions (2 pts)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
HOMEWORK 10: Tiktok Trends
|
HOMEWORK 9: Tiktok Trends
|
||||||
|
|
||||||
|
|
||||||
NAME: < insert name >
|
NAME: Jinshan Zhou
|
||||||
|
|
||||||
|
|
||||||
COLLABORATORS AND OTHER RESOURCES:
|
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
|
LMS, etc.), and all of the resources (books, online reference
|
||||||
material, etc.) you consulted in completing this assignment.
|
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
|
Remember: Your implementation for this assignment must be done on your
|
||||||
own, as described in "Academic Integrity for Homework" handout.
|
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:
|
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
|
## 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
|
finally "clicked" for you in the process of working on this assignment? How well
|
||||||
did the development and testing process go for you?
|
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).
|
||||||
|
|||||||
35
hws/tiktok_trends/SoundInfo.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
15
hws/tiktok_trends/StringInterner.cpp
Normal 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("");
|
||||||
|
}
|
||||||
15
hws/tiktok_trends/StringInterner.h
Normal 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();
|
||||||
|
};
|
||||||
18
hws/tiktok_trends/StringPtrUtils.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
32
hws/tiktok_trends/TopKVideoHolder.cpp
Normal 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(); }
|
||||||
19
hws/tiktok_trends/TopKVideoHolder.h
Normal 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
@@ -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
@@ -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);
|
||||||
37
hws/tiktok_trends/VideoInfo.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||