Compare commits
49 Commits
bb359c9fe7
...
a109046498
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
16
.vscode/launch.json
vendored
@@ -135,6 +135,22 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
@@ -78,6 +78,10 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
439
animations/heap/sort/heap-sort.js
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
//AUTHOR: Chloe Lee
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
// Display code with line numbers
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// !! subject to change !!
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = [];
|
||||||
|
let tempArray = [...array];
|
||||||
|
const n = tempArray.length;
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...tempArray],
|
||||||
|
line: 0,
|
||||||
|
description: 'Starting heap sort with array [' + tempArray.join(', ') + ']',
|
||||||
|
heapSize: n
|
||||||
|
});
|
||||||
|
|
||||||
|
// build max-heap
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...tempArray],
|
||||||
|
line: 2,
|
||||||
|
description: 'Building max heap',
|
||||||
|
heapSize: n
|
||||||
|
});
|
||||||
|
|
||||||
|
let sortedIndices = [];
|
||||||
|
|
||||||
|
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...tempArray],
|
||||||
|
line: 3,
|
||||||
|
description: `Heapifying subtree rooted at index ${i}`,
|
||||||
|
heapSize: n,
|
||||||
|
comparing: [i]
|
||||||
|
});
|
||||||
|
|
||||||
|
heapifyWithAnimation(tempArray, n, i, sortedIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...tempArray],
|
||||||
|
line: 6,
|
||||||
|
description: 'Max heap built. Extracting elements one by one',
|
||||||
|
heapSize: n
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = n - 1; i > 0; i--) {
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...tempArray],
|
||||||
|
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: [...tempArray],
|
||||||
|
line: 8,
|
||||||
|
description: `Element ${tempArray[i]} is now in its correct position`,
|
||||||
|
heapSize: i,
|
||||||
|
sorted: [...sortedIndices]
|
||||||
|
});
|
||||||
|
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...tempArray],
|
||||||
|
line: 11,
|
||||||
|
description: `Heapifying reduced heap (size ${i})`,
|
||||||
|
heapSize: i
|
||||||
|
});
|
||||||
|
|
||||||
|
heapifyWithAnimation(tempArray, i, 0, sortedIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedIndices.push(0);
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...tempArray],
|
||||||
|
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) {
|
||||||
|
let largest = i;
|
||||||
|
const left = 2 * i + 1;
|
||||||
|
const right = 2 * i + 2;
|
||||||
|
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...arr],
|
||||||
|
line: 16,
|
||||||
|
description: `Heapifying at index ${i}`,
|
||||||
|
heapSize: n,
|
||||||
|
comparing: [i],
|
||||||
|
sorted: sortedIndices ? [...sortedIndices] : []
|
||||||
|
});
|
||||||
|
|
||||||
|
// compare with left child
|
||||||
|
if (left < n) {
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...arr],
|
||||||
|
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: [...arr],
|
||||||
|
line: 22,
|
||||||
|
description: `Left child ${arr[left]} is larger than current largest ${arr[i]}`,
|
||||||
|
heapSize: n,
|
||||||
|
comparing: [largest],
|
||||||
|
sorted: sortedIndices ? [...sortedIndices] : []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare with right child
|
||||||
|
if (right < n) {
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...arr],
|
||||||
|
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: [...arr],
|
||||||
|
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] : []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if largest is not root, swap and heapify
|
||||||
|
if (largest !== i) {
|
||||||
|
animationSteps.push({
|
||||||
|
array: [...arr],
|
||||||
|
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: [...arr],
|
||||||
|
line: 33,
|
||||||
|
description: `Recursively heapifying the affected subtree rooted at index ${largest}`,
|
||||||
|
heapSize: n,
|
||||||
|
comparing: [largest],
|
||||||
|
sorted: sortedIndices ? [...sortedIndices] : []
|
||||||
|
});
|
||||||
|
|
||||||
|
heapifyWithAnimation(arr, n, largest, sortedIndices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextStep() {
|
||||||
|
if (currentStep < animationSteps.length - 1) {
|
||||||
|
currentStep++;
|
||||||
|
|
||||||
|
// update the array to current state
|
||||||
|
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>
|
||||||
594
animations/trees/morris/morrisPostOrder.html
Normal file
@@ -0,0 +1,594 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Morris Post‑Order Traversal Animation & Code Display</title>
|
||||||
|
<style>
|
||||||
|
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: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 88vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-container {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
width: 48%;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 88vh;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: Consolas, "Courier New", monospace;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number {
|
||||||
|
color: #666;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-container {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
width: 48%;
|
||||||
|
height: 88vh;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- Load Konva.js -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/konva@8.3.13/konva.min.js"></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.</p>
|
||||||
|
|
||||||
|
<div class="visualization-container">
|
||||||
|
<div class="code-container">
|
||||||
|
<pre><code><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> }</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>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 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) {
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.reversedEdges) {
|
||||||
|
node.reversedEdges.forEach(edge => edge.destroy());
|
||||||
|
node.reversedEdges = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let current = head;
|
||||||
|
while (current !== null && current.right !== null) {
|
||||||
|
if (!current.rightEdge) {
|
||||||
|
current.rightEdge = createArrow(current, current.right, '#888');
|
||||||
|
current.rightEdge.moveToBottom();
|
||||||
|
}
|
||||||
|
current = current.right;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
await visualizeRestore(head);
|
||||||
|
let prev = null, curr = head, next;
|
||||||
|
while (curr !== null) {
|
||||||
|
next = curr.right;
|
||||||
|
curr.right = prev;
|
||||||
|
prev = curr;
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse traverse the right edge of a subtree.
|
||||||
|
async function reverseTraverseRightEdge(head) {
|
||||||
|
if (!head) return;
|
||||||
|
updateExplanation("Reversing the right edge starting from 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) {
|
||||||
|
let current = root;
|
||||||
|
while (current !== null) {
|
||||||
|
highlightNode(current, '#ffeb3b');
|
||||||
|
layer.draw();
|
||||||
|
if (current.left !== null) {
|
||||||
|
updateExplanation("Current: " + current.val + " - Has left child, finding rightmost in left subtree");
|
||||||
|
await waitForNext();
|
||||||
|
let rightmost = current.left;
|
||||||
|
highlightNode(rightmost, '#2196f3');
|
||||||
|
layer.draw();
|
||||||
|
while (rightmost.right !== null && rightmost.right !== current) {
|
||||||
|
updateExplanation("Moving to right: " + rightmost.val);
|
||||||
|
await waitForNext();
|
||||||
|
unhighlightNode(rightmost, 'white');
|
||||||
|
rightmost = rightmost.right;
|
||||||
|
highlightNode(rightmost, '#2196f3');
|
||||||
|
layer.draw();
|
||||||
|
}
|
||||||
|
if (rightmost.right === null) {
|
||||||
|
updateExplanation("Rightmost: " + rightmost.val + " - Creating thread to current: " + current.val);
|
||||||
|
await waitForNext();
|
||||||
|
createThreadEdge(rightmost, current);
|
||||||
|
rightmost.right = current;
|
||||||
|
highlightNode(rightmost, '#03a9f4');
|
||||||
|
layer.draw();
|
||||||
|
await waitForNext();
|
||||||
|
unhighlightNode(rightmost, 'white');
|
||||||
|
unhighlightNode(current, 'white');
|
||||||
|
current = current.left;
|
||||||
|
} else {
|
||||||
|
updateExplanation("Thread detected at rightmost: " + rightmost.val + " - Removing thread and processing subtree");
|
||||||
|
await waitForNext();
|
||||||
|
removeThreadEdge(rightmost);
|
||||||
|
rightmost.right = null;
|
||||||
|
unhighlightNode(rightmost, 'white');
|
||||||
|
layer.draw();
|
||||||
|
await reverseTraverseRightEdge(current.left);
|
||||||
|
unhighlightNode(current, 'white');
|
||||||
|
current = current.right;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateExplanation("Current: " + current.val + " - No left child, moving right");
|
||||||
|
await waitForNext();
|
||||||
|
unhighlightNode(current, 'white');
|
||||||
|
current = current.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateExplanation("Processing final right edge");
|
||||||
|
await waitForNext();
|
||||||
|
await reverseTraverseRightEdge(root);
|
||||||
|
updateExplanation("Morris Post-Order Traversal complete");
|
||||||
|
await waitForNext();
|
||||||
|
updateExplanation("Final post-order traversal result: " + document.getElementById('output').innerText);
|
||||||
|
nodes.forEach(n => {
|
||||||
|
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() {
|
||||||
|
document.getElementById('nextStep').disabled = false;
|
||||||
|
postorderTraversal(node1);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1327
animations/trees/morris/morris_pre_order.html
Normal file
@@ -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)-->
|
||||||
|
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 |
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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
256
hws/tiktok_trends/main.cpp
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <queue>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "Constants.h"
|
||||||
|
#include "StringInterner.h"
|
||||||
|
#include "StringPtrUtils.h"
|
||||||
|
#include "VideoInfo.h"
|
||||||
|
#include "TopKVideoHolder.h"
|
||||||
|
#include "HashtagInfo.h"
|
||||||
|
#include "SoundInfo.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
bool processHashtags(const std::string& filename, std::ofstream& outputFile) {
|
||||||
|
std::ifstream inputFile(filename);
|
||||||
|
if (!inputFile.is_open()) {
|
||||||
|
std::cerr << "Cannot open input file: " << filename << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringInterner interner;
|
||||||
|
std::unordered_map<const std::string*, HashtagInfo, StringPtrHash, StringPtrEqual> hashtagData;
|
||||||
|
hashtagData.reserve(250000);
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
int inputOrderCounter = 0;
|
||||||
|
VideoInfo currentVideo;
|
||||||
|
std::string text_buffer;
|
||||||
|
|
||||||
|
while (std::getline(inputFile, line)) {
|
||||||
|
if (line.length() < 10) continue;
|
||||||
|
inputOrderCounter++;
|
||||||
|
|
||||||
|
if (parseLineForHashtags(line, inputOrderCounter, interner, currentVideo, text_buffer)) {
|
||||||
|
if (!text_buffer.empty()) {
|
||||||
|
extractHashtags(text_buffer, hashtagData, interner, currentVideo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputFile.close();
|
||||||
|
|
||||||
|
std::priority_queue<HashtagInfo*, std::vector<HashtagInfo*>, CompareHashtagPtrForHeap> top20Hashtags;
|
||||||
|
typedef std::unordered_map<const std::string*, HashtagInfo, StringPtrHash, StringPtrEqual> HashtagMapType;
|
||||||
|
|
||||||
|
for (HashtagMapType::iterator it = hashtagData.begin(); it != hashtagData.end(); ++it) {
|
||||||
|
HashtagInfo* currentHashtagPtr = &(it->second);
|
||||||
|
|
||||||
|
if (top20Hashtags.size() < TOP_N_OUTPUT) {
|
||||||
|
top20Hashtags.push(currentHashtagPtr);
|
||||||
|
} else {
|
||||||
|
const HashtagInfo* topPtr = top20Hashtags.top();
|
||||||
|
bool is_better = CompareHashtagPtr()(currentHashtagPtr, topPtr);
|
||||||
|
|
||||||
|
if (is_better) {
|
||||||
|
top20Hashtags.pop();
|
||||||
|
top20Hashtags.push(currentHashtagPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HashtagInfo*> finalTop20;
|
||||||
|
finalTop20.reserve(top20Hashtags.size());
|
||||||
|
while (!top20Hashtags.empty()) {
|
||||||
|
finalTop20.push_back(top20Hashtags.top());
|
||||||
|
top20Hashtags.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(finalTop20.begin(), finalTop20.end(), CompareHashtagPtr());
|
||||||
|
|
||||||
|
outputFile << "trending hashtags:\n\n";
|
||||||
|
for (size_t i = 0; i < finalTop20.size(); ++i) {
|
||||||
|
HashtagInfo* currentHashtag = finalTop20[i];
|
||||||
|
|
||||||
|
outputFile << "========================\n";
|
||||||
|
|
||||||
|
outputFile << "#" << (currentHashtag->name ? *currentHashtag->name : "null") << "\n";
|
||||||
|
outputFile << "used " << currentHashtag->usageCount << " times\n";
|
||||||
|
outputFile << currentHashtag->totalViews << " views\n\n";
|
||||||
|
|
||||||
|
extractSortAndPrintTop3Videos(outputFile, currentHashtag->topVideos);
|
||||||
|
|
||||||
|
outputFile << "========================";
|
||||||
|
if (i < finalTop20.size() - 1) {
|
||||||
|
outputFile << "\n";
|
||||||
|
} else {
|
||||||
|
outputFile << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool processSounds(const std::string& filename, std::ofstream& outputFile) {
|
||||||
|
std::ifstream inputFile(filename);
|
||||||
|
if (!inputFile.is_open()) {
|
||||||
|
std::cerr << "Cannot open input file: " << filename << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringInterner interner;
|
||||||
|
std::unordered_map<const std::string*, SoundInfo, StringPtrHash, StringPtrEqual> soundData;
|
||||||
|
soundData.reserve(50000);
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
int inputOrderCounter = 0;
|
||||||
|
VideoInfo currentVideo;
|
||||||
|
const std::string* musicIdPtr = nullptr;
|
||||||
|
const std::string* musicNamePtr = nullptr;
|
||||||
|
const std::string* musicAuthorPtr = nullptr;
|
||||||
|
|
||||||
|
while (std::getline(inputFile, line)) {
|
||||||
|
if (line.length() < 10) continue;
|
||||||
|
inputOrderCounter++;
|
||||||
|
|
||||||
|
musicIdPtr = nullptr;
|
||||||
|
musicNamePtr = nullptr;
|
||||||
|
musicAuthorPtr = nullptr;
|
||||||
|
|
||||||
|
if (parseLineForSounds(line, inputOrderCounter, interner, currentVideo,
|
||||||
|
musicIdPtr, musicNamePtr, musicAuthorPtr))
|
||||||
|
{
|
||||||
|
if (musicIdPtr == nullptr || musicIdPtr->empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef std::unordered_map<const std::string*, SoundInfo, StringPtrHash, StringPtrEqual> SoundMapType;
|
||||||
|
SoundMapType::iterator it = soundData.find(musicIdPtr);
|
||||||
|
|
||||||
|
if (it == soundData.end()) {
|
||||||
|
std::pair<SoundMapType::iterator, bool> emplace_result =
|
||||||
|
soundData.emplace(musicIdPtr, SoundInfo(musicIdPtr, musicNamePtr, musicAuthorPtr));
|
||||||
|
it = emplace_result.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
it->second.totalViews += currentVideo.playCount;
|
||||||
|
|
||||||
|
if (it->second.musicName->empty() && !musicNamePtr->empty()) {
|
||||||
|
it->second.musicName = musicNamePtr;
|
||||||
|
}
|
||||||
|
if (it->second.musicAuthor->empty() && !musicAuthorPtr->empty()) {
|
||||||
|
it->second.musicAuthor = musicAuthorPtr;
|
||||||
|
}
|
||||||
|
it->second.topVideos.add(currentVideo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputFile.close();
|
||||||
|
|
||||||
|
std::priority_queue<SoundInfo*, std::vector<SoundInfo*>, CompareSoundPtrForHeap> top20Sounds;
|
||||||
|
typedef std::unordered_map<const std::string*, SoundInfo, StringPtrHash, StringPtrEqual> SoundMapType;
|
||||||
|
|
||||||
|
for (SoundMapType::iterator it = soundData.begin(); it != soundData.end(); ++it) {
|
||||||
|
SoundInfo* currentSoundPtr = &(it->second);
|
||||||
|
|
||||||
|
if (top20Sounds.size() < TOP_N_OUTPUT) {
|
||||||
|
top20Sounds.push(currentSoundPtr);
|
||||||
|
} else {
|
||||||
|
const SoundInfo* topPtr = top20Sounds.top();
|
||||||
|
bool is_better = CompareSoundPtr()(currentSoundPtr, topPtr);
|
||||||
|
|
||||||
|
if (is_better) {
|
||||||
|
top20Sounds.pop();
|
||||||
|
top20Sounds.push(currentSoundPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SoundInfo*> finalTop20;
|
||||||
|
finalTop20.reserve(top20Sounds.size());
|
||||||
|
while (!top20Sounds.empty()) {
|
||||||
|
finalTop20.push_back(top20Sounds.top());
|
||||||
|
top20Sounds.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(finalTop20.begin(), finalTop20.end(), CompareSoundPtr());
|
||||||
|
|
||||||
|
outputFile << "trending sounds:\n\n";
|
||||||
|
for (size_t i = 0; i < finalTop20.size(); ++i) {
|
||||||
|
SoundInfo* currentSound = finalTop20[i];
|
||||||
|
|
||||||
|
outputFile << "========================\n";
|
||||||
|
|
||||||
|
if (currentSound->musicName == nullptr || currentSound->musicName->empty()) {
|
||||||
|
outputFile << "\n";
|
||||||
|
} else {
|
||||||
|
outputFile << *currentSound->musicName << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFile << currentSound->totalViews << " views\n";
|
||||||
|
|
||||||
|
if (currentSound->musicAuthor == nullptr || currentSound->musicAuthor->empty()) {
|
||||||
|
outputFile << "\n";
|
||||||
|
} else {
|
||||||
|
outputFile << *currentSound->musicAuthor << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFile << "music id: " << (currentSound->musicId && !currentSound->musicId->empty() ? *currentSound->musicId : "null") << "\n";
|
||||||
|
|
||||||
|
if (!currentSound->topVideos.empty()) {
|
||||||
|
outputFile << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
extractSortAndPrintTop3Videos(outputFile, currentSound->topVideos);
|
||||||
|
|
||||||
|
outputFile << "========================";
|
||||||
|
if (i < finalTop20.size() - 1) {
|
||||||
|
outputFile << "\n";
|
||||||
|
} else {
|
||||||
|
outputFile << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc != 4) {
|
||||||
|
std::cerr << "Usage: nytrends.exe <input.json> <output.txt> <mode>\n";
|
||||||
|
std::cerr << "Mode can be 'hashtag' or 'sound'\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::string inputFileName = argv[1];
|
||||||
|
std::string outputFileName = argv[2];
|
||||||
|
std::string mode = argv[3];
|
||||||
|
|
||||||
|
std::ofstream outputFile(outputFileName);
|
||||||
|
if (!outputFile.is_open()) {
|
||||||
|
std::cerr << "Error: Cannot open output file " << outputFileName << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ios_base::sync_with_stdio(false);
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
if (mode == "hashtag") {
|
||||||
|
success = processHashtags(inputFileName, outputFile);
|
||||||
|
} else if (mode == "sound") {
|
||||||
|
success = processSounds(inputFileName, outputFile);
|
||||||
|
} else {
|
||||||
|
std::cerr << "Error: Invalid mode '" << mode << "'. Must be 'hashtag' or 'sound'." << std::endl;
|
||||||
|
outputFile.close();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFile.close();
|
||||||
|
return success ? 0 : 1;
|
||||||
|
}
|
||||||
242
hws/tiktok_trends/output.txt
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
trending hashtags:
|
||||||
|
|
||||||
|
========================
|
||||||
|
#fyp
|
||||||
|
used 7600 times
|
||||||
|
261199234341 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/567504ab3e4648dea968213ce979f281?x-expires=1700449200&x-signature=bjGEgY4bdEVOMMHQa2S0qrzNCQY%3D
|
||||||
|
web video url: https://www.tiktok.com/@bellapoarch/video/6862153058223197445
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/567504ab3e4648dea968213ce979f281?x-expires=1700449200&x-signature=bjGEgY4bdEVOMMHQa2S0qrzNCQY%3D
|
||||||
|
web video url: https://www.tiktok.com/@bellapoarch/video/6862153058223197445
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oAJCgD1khIANGRcTLhqQZNCi3ohAuAzoyEdIaf?x-expires=1700449200&x-signature=hu1Kg0Cpz%2BzVRXqYkv%2Fl6E8%2Ftgk%3D
|
||||||
|
web video url: https://www.tiktok.com/@tool_tips/video/7212981630904864005
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#foryou
|
||||||
|
used 2765 times
|
||||||
|
92282640558 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/c4c7c98ecb5f4a8980ed7d58cdea2df3_1676378432?x-expires=1700449200&x-signature=QIchR40Etr%2BAjbAuzYbwTKnD7dA%3D
|
||||||
|
web video url: https://www.tiktok.com/@gorillatiks/video/7199990500512894213
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/d57bd10bd2594b148d48c5443d5571b0?x-expires=1700449200&x-signature=Z%2FTgQwhQ9eSmRMF3cBmH%2BdVHve8%3D
|
||||||
|
web video url: https://www.tiktok.com/@honeycats77/video/7190528800352980267
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/d57bd10bd2594b148d48c5443d5571b0?x-expires=1700449200&x-signature=Z%2FTgQwhQ9eSmRMF3cBmH%2BdVHve8%3D
|
||||||
|
web video url: https://www.tiktok.com/@honeycats77/video/7190528800352980267
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#viral
|
||||||
|
used 1759 times
|
||||||
|
59270543842 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o4n0aDMCxQPkleFE5JnbeaoIw9uEBRQiTkIzAB?x-expires=1700449200&x-signature=zOxX4QIMqL%2BNOyl6R57PLiVKb%2BE%3D
|
||||||
|
web video url: https://www.tiktok.com/@dada_ahoufe_/video/7247202774696447238
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o4n0aDMCxQPkleFE5JnbeaoIw9uEBRQiTkIzAB?x-expires=1700449200&x-signature=zOxX4QIMqL%2BNOyl6R57PLiVKb%2BE%3D
|
||||||
|
web video url: https://www.tiktok.com/@dada_ahoufe_/video/7247202774696447238
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oYRUDAt9kFIA2SIwCWomEVfha623AyrLzxgaAo?x-expires=1700449200&x-signature=xVeyOReZuykD9rFS4KFcN%2FFL44g%3D
|
||||||
|
web video url: https://www.tiktok.com/@carrosseriereparation/video/7217942797360303365
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#makeuptutorial
|
||||||
|
used 1709 times
|
||||||
|
22311707100 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/ea183fe6de594a319ba917d1ffbff11b?x-expires=1700503200&x-signature=9L4ypK162uI%2BirECcDcFqctjvn8%3D
|
||||||
|
web video url: https://www.tiktok.com/@dollievision/video/7208244986666585386
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/ea183fe6de594a319ba917d1ffbff11b?x-expires=1700503200&x-signature=9L4ypK162uI%2BirECcDcFqctjvn8%3D
|
||||||
|
web video url: https://www.tiktok.com/@dollievision/video/7208244986666585386
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/5461c70dd8ee4a0d891e7f2529f6b8ea_1670789072?x-expires=1700503200&x-signature=TqqnBqyBh5cnb150Ri0jXfwaL9s%3D
|
||||||
|
web video url: https://www.tiktok.com/@alicekingmakeup/video/7175984394950167813
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#couplestiktok
|
||||||
|
used 1610 times
|
||||||
|
14706422100 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/693775bbddac4df4ad008ff880041fbc?x-expires=1700503200&x-signature=UU8VVoLrIaXIVFnYLf3jl8IYO%2BE%3D
|
||||||
|
web video url: https://www.tiktok.com/@misiaaa621/video/7149368989611773227
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/693775bbddac4df4ad008ff880041fbc?x-expires=1700503200&x-signature=UU8VVoLrIaXIVFnYLf3jl8IYO%2BE%3D
|
||||||
|
web video url: https://www.tiktok.com/@misiaaa621/video/7149368989611773227
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/4b83dfa1cc0c47318e408a6bcde34bb6_1677549193?x-expires=1700503200&x-signature=%2FN8FYhRpVldGSaxbP6dgYeEroqI%3D
|
||||||
|
web video url: https://www.tiktok.com/@debbiekval/video/7205018880622857515
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#lifehack
|
||||||
|
used 1585 times
|
||||||
|
33681856600 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-sg.tiktokcdn.com/tos-alisg-p-0037/d6a1c1c323614919975fad3ee1c1ef9e~tplv-dmt-logom:tos-alisg-i-0000/4124427fcd3045968ac1c3136bd92d6c.image?x-expires=1700452800&x-signature=qCaN1hrF7pqQ0kvZJnlFnc9jI6Q%3D
|
||||||
|
web video url: https://www.tiktok.com/@tresorfie/video/7039091515863403778
|
||||||
|
cover url: https://p16-sign-sg.tiktokcdn.com/tos-alisg-p-0037/d6a1c1c323614919975fad3ee1c1ef9e~tplv-dmt-logom:tos-alisg-i-0000/4124427fcd3045968ac1c3136bd92d6c.image?x-expires=1700449200&x-signature=WSl3XKN1HPXy7jpguj8v0AaI3FU%3D
|
||||||
|
web video url: https://www.tiktok.com/@tresorfie/video/7039091515863403778
|
||||||
|
cover url: https://p16-sign-sg.tiktokcdn.com/tos-alisg-p-0037/501627c6b36849e282740c764611f2a7_1634994542~tplv-dmt-logom:tos-alisg-pv-0037/f3273e6f3e92421d860be8f5e72ac0bd.image?x-expires=1700452800&x-signature=DkwRLgyyY5ec0757c1hCq372yJM%3D
|
||||||
|
web video url: https://www.tiktok.com/@issei0806/video/7022248055625846018
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#funnyvideos
|
||||||
|
used 1573 times
|
||||||
|
67029374400 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-useast2a.tiktokcdn.com/obj/tos-useast2a-p-0037-euttp/b0fed04ac06b45f58a9c3add061342dd_1686566824?x-expires=1700449200&x-signature=HTd5Yy2XA1y%2Bn0Gy2PnX9t%2FNpw4%3D
|
||||||
|
web video url: https://www.tiktok.com/@funnnyh/video/7243749070475496731
|
||||||
|
cover url: https://p16-sign-useast2a.tiktokcdn.com/obj/tos-useast2a-p-0037-euttp/b0fed04ac06b45f58a9c3add061342dd_1686566824?x-expires=1700449200&x-signature=HTd5Yy2XA1y%2Bn0Gy2PnX9t%2FNpw4%3D
|
||||||
|
web video url: https://www.tiktok.com/@funnnyh/video/7243749070475496731
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-useast2a-p-0037-euttp/b0fed04ac06b45f58a9c3add061342dd_1686566824?x-expires=1700449200&x-signature=vv04JjjwKgR1P3t117v%2B5HMvnpI%3D
|
||||||
|
web video url: https://www.tiktok.com/@funnnyh/video/7243749070475496731
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#foryoupage
|
||||||
|
used 1550 times
|
||||||
|
49067115500 views
|
||||||
|
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/d57bd10bd2594b148d48c5443d5571b0?x-expires=1700449200&x-signature=Z%2FTgQwhQ9eSmRMF3cBmH%2BdVHve8%3D
|
||||||
|
web video url: https://www.tiktok.com/@honeycats77/video/7190528800352980267
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/d57bd10bd2594b148d48c5443d5571b0?x-expires=1700449200&x-signature=Z%2FTgQwhQ9eSmRMF3cBmH%2BdVHve8%3D
|
||||||
|
web video url: https://www.tiktok.com/@honeycats77/video/7190528800352980267
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oYRUDAt9kFIA2SIwCWomEVfha623AyrLzxgaAo?x-expires=1700449200&x-signature=xVeyOReZuykD9rFS4KFcN%2FFL44g%3D
|
||||||
|
web video url: https://www.tiktok.com/@carrosseriereparation/video/7217942797360303365
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#newyorkcity
|
||||||
|
used 1545 times
|
||||||
|
8642836600 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/08b82fdf19d5468e91a032b30e527861_1692785637?x-expires=1700452800&x-signature=%2F5sW2i0xTGXJJj2PKbwI6VXywWI%3D
|
||||||
|
web video url: https://www.tiktok.com/@erikconover/video/7270458709065731370
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/1ea2545bde2645ec8f1106a4b9de6c2e_1648602515?x-expires=1700452800&x-signature=HNr6UCQsc4q0m%2FFShx%2FJJNWb1Jg%3D
|
||||||
|
web video url: https://www.tiktok.com/@thekatieromero/video/7080693879141485870
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/c898b993f308477e92334437f9f0e1e1?x-expires=1700452800&x-signature=ZNHG9%2FMBw9qp5DSm%2BNnbhwX6xK8%3D
|
||||||
|
web video url: https://www.tiktok.com/@thekatieromero/video/7105552208422374699
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#ifweeverbrokeup
|
||||||
|
used 1543 times
|
||||||
|
1337044198 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-sg.tiktokcdn.com/obj/tos-alisg-p-0037/ee79eb2bea6445739ed71cef3e9b84b6_1686646723?x-expires=1700456400&x-signature=0MADrs89I23eeCudb%2FJxkI%2FJbR8%3D
|
||||||
|
web video url: https://www.tiktok.com/@zanmangloopyofficial/video/7244092495129218312
|
||||||
|
cover url: https://p16-sign-sg.tiktokcdn.com/obj/tos-alisg-p-0037/ee79eb2bea6445739ed71cef3e9b84b6_1686646723?x-expires=1700456400&x-signature=0MADrs89I23eeCudb%2FJxkI%2FJbR8%3D
|
||||||
|
web video url: https://www.tiktok.com/@zanmangloopyofficial/video/7244092495129218312
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/7895be3e406d435ba0db9e6f5db349e2?x-expires=1700456400&x-signature=ahrkOjcQRlDhAOf1upGEu%2B2ECYU%3D
|
||||||
|
web video url: https://www.tiktok.com/@bebopandbebe/video/7238437685537328426
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#springcleaning
|
||||||
|
used 1416 times
|
||||||
|
2156123000 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/56ce7e79491a4b27b371517ce134fa82_1631381225~tplv-dmt-logom:tos-maliva-p-0000/415cfd01b3484fb38f7b088aa6efda67.image?x-expires=1700503200&x-signature=YL4yGwa%2F1gZ59cKHMov7ficsK9E%3D
|
||||||
|
web video url: https://www.tiktok.com/@livecomposed/video/7006728991067491589
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/4c54b4d1332c4000a615d9c5fc172be8_1677780053?x-expires=1700503200&x-signature=aD4zRpPLbhn9wz4vtTQWZRa2I1U%3D
|
||||||
|
web video url: https://www.tiktok.com/@atmeikasa/video/7206010371004583214
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/4c54b4d1332c4000a615d9c5fc172be8_1677780053?x-expires=1700503200&x-signature=aD4zRpPLbhn9wz4vtTQWZRa2I1U%3D
|
||||||
|
web video url: https://www.tiktok.com/@atmeikasa/video/7206010371004583214
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#funny
|
||||||
|
used 1382 times
|
||||||
|
53648909500 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-useast2a.tiktokcdn.com/obj/tos-useast2a-p-0037-euttp/b0fed04ac06b45f58a9c3add061342dd_1686566824?x-expires=1700449200&x-signature=HTd5Yy2XA1y%2Bn0Gy2PnX9t%2FNpw4%3D
|
||||||
|
web video url: https://www.tiktok.com/@funnnyh/video/7243749070475496731
|
||||||
|
cover url: https://p16-sign-useast2a.tiktokcdn.com/obj/tos-useast2a-p-0037-euttp/b0fed04ac06b45f58a9c3add061342dd_1686566824?x-expires=1700449200&x-signature=HTd5Yy2XA1y%2Bn0Gy2PnX9t%2FNpw4%3D
|
||||||
|
web video url: https://www.tiktok.com/@funnnyh/video/7243749070475496731
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-useast2a-p-0037-euttp/b0fed04ac06b45f58a9c3add061342dd_1686566824?x-expires=1700449200&x-signature=vv04JjjwKgR1P3t117v%2B5HMvnpI%3D
|
||||||
|
web video url: https://www.tiktok.com/@funnnyh/video/7243749070475496731
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#happymonday
|
||||||
|
used 1308 times
|
||||||
|
741991700 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/oQgeH8BRJnj20JEFoQ5tAf1MIb976nBD89QiFB~tplv-dmt-logom:tos-useast2a-v-0068/4763cd9418ac4d7faccbf52906bcf43c.image?x-expires=1700449200&x-signature=DPRcWm2Xhpe7r2HmxxGBzOyhwVs%3D
|
||||||
|
web video url: https://www.tiktok.com/@joinparallel.io/video/7192338389255916806
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/oQgeH8BRJnj20JEFoQ5tAf1MIb976nBD89QiFB~tplv-dmt-logom:tos-useast2a-v-0068/4763cd9418ac4d7faccbf52906bcf43c.image?x-expires=1700449200&x-signature=DPRcWm2Xhpe7r2HmxxGBzOyhwVs%3D
|
||||||
|
web video url: https://www.tiktok.com/@joinparallel.io/video/7192338389255916806
|
||||||
|
cover url: https://p16-sign-sg.tiktokcdn.com/obj/tos-alisg-p-0037/002965b791d641d5b2f3d86ee0019604_1675130079?x-expires=1700449200&x-signature=TMCJOBJCXYeu5rsjFWpohtGwT8M%3D
|
||||||
|
web video url: https://www.tiktok.com/@mondayhaircare/video/7194628805414161665
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#nyc
|
||||||
|
used 990 times
|
||||||
|
5577241000 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/08b82fdf19d5468e91a032b30e527861_1692785637?x-expires=1700452800&x-signature=%2F5sW2i0xTGXJJj2PKbwI6VXywWI%3D
|
||||||
|
web video url: https://www.tiktok.com/@erikconover/video/7270458709065731370
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/1ea2545bde2645ec8f1106a4b9de6c2e_1648602515?x-expires=1700452800&x-signature=HNr6UCQsc4q0m%2FFShx%2FJJNWb1Jg%3D
|
||||||
|
web video url: https://www.tiktok.com/@thekatieromero/video/7080693879141485870
|
||||||
|
cover url: https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/c898b993f308477e92334437f9f0e1e1?x-expires=1700452800&x-signature=ZNHG9%2FMBw9qp5DSm%2BNnbhwX6xK8%3D
|
||||||
|
web video url: https://www.tiktok.com/@thekatieromero/video/7105552208422374699
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#makeup
|
||||||
|
used 976 times
|
||||||
|
15309874500 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/ea183fe6de594a319ba917d1ffbff11b?x-expires=1700503200&x-signature=9L4ypK162uI%2BirECcDcFqctjvn8%3D
|
||||||
|
web video url: https://www.tiktok.com/@dollievision/video/7208244986666585386
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/ea183fe6de594a319ba917d1ffbff11b?x-expires=1700503200&x-signature=9L4ypK162uI%2BirECcDcFqctjvn8%3D
|
||||||
|
web video url: https://www.tiktok.com/@dollievision/video/7208244986666585386
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/1404c560d1e74fe7881a0a4ae6414de5_1652622380?x-expires=1700449200&x-signature=m%2BIawkKwQBwnUaTqBTTMtqLQPZo%3D
|
||||||
|
web video url: https://www.tiktok.com/@mimles/video/7097959048515013894
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#trending
|
||||||
|
used 721 times
|
||||||
|
21692028406 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/bbdfd4ef0c4040b2bf9c52e9bb81d770?x-expires=1700449200&x-signature=fMm4z9wGlJCa1VFXvU5jQ0ot6tA%3D
|
||||||
|
web video url: https://www.tiktok.com/@phuonglinh.ido/video/7215533760039865646
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/c92407e5bca34ce78eb17db878630adc?x-expires=1700449200&x-signature=836u0V7z2PC7tFMLlsvVDFDU1wU%3D
|
||||||
|
web video url: https://www.tiktok.com/@asmr.mus/video/7212985350124375342
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/c92407e5bca34ce78eb17db878630adc?x-expires=1700449200&x-signature=836u0V7z2PC7tFMLlsvVDFDU1wU%3D
|
||||||
|
web video url: https://www.tiktok.com/@asmr.mus/video/7212985350124375342
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#comedy
|
||||||
|
used 579 times
|
||||||
|
14364510900 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/49a912da569f4c69b3658762357f3922_1572472757?x-expires=1700449200&x-signature=rCokiz5pbl88BrzDzX3AB1LFCXg%3D
|
||||||
|
web video url: https://www.tiktok.com/@kisonkee/video/6753718966637677830
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/49a912da569f4c69b3658762357f3922_1572472757?x-expires=1700449200&x-signature=rCokiz5pbl88BrzDzX3AB1LFCXg%3D
|
||||||
|
web video url: https://www.tiktok.com/@kisonkee/video/6753718966637677830
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o0EnyBknREPO4GdeDo4nxAIJRFJfbfzAzGQSDf?x-expires=1700449200&x-signature=zuGbpMoTS01F4waRsGo2r2AoVxk%3D
|
||||||
|
web video url: https://www.tiktok.com/@ricoanimations0/video/7241573984590957830
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#newyork
|
||||||
|
used 555 times
|
||||||
|
3126420800 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/4a878de5dbe241b5b5e25635f4200a51_1650915958?x-expires=1700449200&x-signature=1l%2F8aGh0jktub1R%2BX23PAe64Dys%3D
|
||||||
|
web video url: https://www.tiktok.com/@mdmotivator/video/7090629995546070277
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/4a878de5dbe241b5b5e25635f4200a51_1650915958?x-expires=1700449200&x-signature=1l%2F8aGh0jktub1R%2BX23PAe64Dys%3D
|
||||||
|
web video url: https://www.tiktok.com/@mdmotivator/video/7090629995546070277
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/08b82fdf19d5468e91a032b30e527861_1692785637?x-expires=1700452800&x-signature=%2F5sW2i0xTGXJJj2PKbwI6VXywWI%3D
|
||||||
|
web video url: https://www.tiktok.com/@erikconover/video/7270458709065731370
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#couple
|
||||||
|
used 439 times
|
||||||
|
5628511600 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/014d01e5b7f848fc8f8899e88e8fa483?x-expires=1700449200&x-signature=IMmyHEigmMoVtLEuTWPZwe%2Fksb0%3D
|
||||||
|
web video url: https://www.tiktok.com/@mamalindy/video/7079555791962885419
|
||||||
|
cover url: https://p16-sign-useast2a.tiktokcdn.com/obj/tos-useast2a-p-0037-aiso/cf3359fa45444f0994cf0dcc1c201b2d_1681930130?x-expires=1700449200&x-signature=tABQmhr%2FtklzlsNqYWGZnNrxwhI%3D
|
||||||
|
web video url: https://www.tiktok.com/@kajsablock/video/7223834852305456410
|
||||||
|
cover url: https://p16-sign-useast2a.tiktokcdn.com/obj/tos-useast2a-p-0037-aiso/cf3359fa45444f0994cf0dcc1c201b2d_1681930130?x-expires=1700449200&x-signature=tABQmhr%2FtklzlsNqYWGZnNrxwhI%3D
|
||||||
|
web video url: https://www.tiktok.com/@kajsablock/video/7223834852305456410
|
||||||
|
========================
|
||||||
|
========================
|
||||||
|
#fy
|
||||||
|
used 397 times
|
||||||
|
16901215000 views
|
||||||
|
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/ef952b508c8043bb8b4ba98e3db850fb_1679074109?x-expires=1700449200&x-signature=AOKtuDMNxX%2BU2b1dRfBvofZLZfk%3D
|
||||||
|
web video url: https://www.tiktok.com/@noelgoescrazy/video/7211568359798803717
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/ef952b508c8043bb8b4ba98e3db850fb_1679074109?x-expires=1700449200&x-signature=AOKtuDMNxX%2BU2b1dRfBvofZLZfk%3D
|
||||||
|
web video url: https://www.tiktok.com/@noelgoescrazy/video/7211568359798803717
|
||||||
|
cover url: https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/509605b7a901400589cd15d6731aaf8c_1677431421?x-expires=1700449200&x-signature=LoywgvGN5XLKIpvwV2UnR6pml6s%3D
|
||||||
|
web video url: https://www.tiktok.com/@noelgoescrazy/video/7204513074097769733
|
||||||
|
========================
|
||||||
456
hws/tiktok_trends/test.py
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
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++"
|
||||||
|
CXXFLAGS = ["-Wall", "-O2", "-std=c++11"]
|
||||||
|
EXECUTABLE = "./nytrends.exe"
|
||||||
|
SOURCE_FILES_PATTERN = "*.cpp"
|
||||||
|
INPUT_DIR = "inputs"
|
||||||
|
EXPECTED_OUTPUT_DIR = "outputs"
|
||||||
|
TEMP_OUTPUT_FILE = "output_unit_test.txt"
|
||||||
|
TEST_TIMEOUT = 120
|
||||||
|
|
||||||
|
# Configuration for memory measurement
|
||||||
|
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
|
||||||
|
SUPPRESS_PROGRAM_OUTPUT = True
|
||||||
|
|
||||||
|
# ANSI Color Codes
|
||||||
|
# ... (colors remain the same) ...
|
||||||
|
COLOR_GREEN = '\033[92m'
|
||||||
|
COLOR_RED = '\033[91m'
|
||||||
|
COLOR_YELLOW = '\033[93m'
|
||||||
|
COLOR_BLUE = '\033[94m'
|
||||||
|
COLOR_RESET = '\033[0m'
|
||||||
|
|
||||||
|
# --- Helper Functions ---
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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() remains the same ---
|
||||||
|
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
|
||||||
|
|
||||||
|
def run_test(test_name, input_file, expected_output_file, argument):
|
||||||
|
"""
|
||||||
|
Runs test, measures time/memory (platform-specific), suppresses output.
|
||||||
|
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
|
||||||
|
|
||||||
|
# Prerequisite checks
|
||||||
|
if not os.path.exists(input_file): return False, "Input file missing", None, None
|
||||||
|
if not os.path.exists(expected_output_file): return False, "Expected output file missing", None, None
|
||||||
|
if not os.path.exists(EXECUTABLE): return False, "Executable not found", None, None
|
||||||
|
|
||||||
|
# --- Command Construction & subprocess args ---
|
||||||
|
base_command = [EXECUTABLE, input_file, TEMP_OUTPUT_FILE, argument]
|
||||||
|
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 ---
|
||||||
|
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) ---
|
||||||
|
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":
|
||||||
|
# Parse memory from captured stderr (process.stderr)
|
||||||
|
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)
|
||||||
|
# Optional: print process.stderr here for debugging
|
||||||
|
# print(f"--- time -l stderr ---\n{process.stderr}\n----------------------")
|
||||||
|
else:
|
||||||
|
print_color(f"Warning: 'maximum resident set size' not found in 'time -l' output (macOS).", COLOR_YELLOW)
|
||||||
|
# Optional: print process.stderr here for debugging
|
||||||
|
# print(f"--- time -l stderr ---\n{process.stderr}\n----------------------")
|
||||||
|
else:
|
||||||
|
print_color(f"Warning: No stderr captured from 'time -l' (macOS).", COLOR_YELLOW)
|
||||||
|
|
||||||
|
# --- Check Program Result ---
|
||||||
|
if process.returncode != 0:
|
||||||
|
print_color(f"Test failed: Program exited with non-zero status {process.returncode}.", COLOR_RED)
|
||||||
|
# Note: program's own stderr might be in process.stderr ONLY if not suppressed AND on macOS
|
||||||
|
# It's generally hidden now by design.
|
||||||
|
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 ---
|
||||||
|
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:
|
||||||
|
# ... (diff printing remains the same) ...
|
||||||
|
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:
|
||||||
|
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 ---
|
||||||
|
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)
|
||||||
|
# Attempt to parse memory ONLY if macOS and stderr might have been partially captured (unlikely but possible)
|
||||||
|
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 # Ignore parsing errors on timeout
|
||||||
|
# Clean up Linux temp file if it exists
|
||||||
|
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)
|
||||||
|
# Clean up Linux temp file if it exists
|
||||||
|
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:
|
||||||
|
# General cleanup (Linux temp file might still exist if parsing failed)
|
||||||
|
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__":
|
||||||
|
# 0. Check if memory measurement is desired AND possible
|
||||||
|
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
|
||||||
|
if not compile_program():
|
||||||
|
print_color("\nCompilation failed. Aborting tests.", COLOR_RED)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 2. Define Test Cases
|
||||||
|
# ... (test_bases and arguments_to_test remain the same) ...
|
||||||
|
test_bases = [
|
||||||
|
("1", "tiny1"), ("2", "tiny2"), ("3", "small1"), ("4", "small2"),
|
||||||
|
("5", "medium1"), ("6", "medium2"), ("7", "large1"), ("8", "large2"),
|
||||||
|
("9", "large3"), ("10", "large4"), ("11", "large5"), ("12", "large6"),
|
||||||
|
("13", "large7"), ("14", "large8"), ("15", "large9"),
|
||||||
|
]
|
||||||
|
arguments_to_test = ["hashtag", "sound"]
|
||||||
|
|
||||||
|
results = {"passed": 0, "failed": 0, "skipped": 0}
|
||||||
|
failed_tests = []
|
||||||
|
test_durations = []
|
||||||
|
test_memory_usages = []
|
||||||
|
|
||||||
|
# 3. Run Tests
|
||||||
|
print_color("\n--- Starting Test Execution ---", COLOR_BLUE)
|
||||||
|
total_start_time = time.perf_counter()
|
||||||
|
|
||||||
|
for id_prefix, base_name in test_bases:
|
||||||
|
for i, argument in enumerate(arguments_to_test, 1):
|
||||||
|
# ... (construct test names/paths) ...
|
||||||
|
test_id = f"{id_prefix}.{i}"
|
||||||
|
test_name = f"Test Case {test_id}: input {base_name}, {argument}"
|
||||||
|
input_filename = os.path.join(INPUT_DIR, f"input_{base_name}.json")
|
||||||
|
expected_output_filename = os.path.join(EXPECTED_OUTPUT_DIR, f"output_{base_name}_{argument}.txt")
|
||||||
|
|
||||||
|
passed, reason, duration, memory_kb = run_test(test_name, input_filename, expected_output_filename, argument)
|
||||||
|
|
||||||
|
# ... (Update results logic remains the same, relies on memory_kb being None if not measured) ...
|
||||||
|
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)
|
||||||
|
elif reason in ["Input file missing", "Expected output file missing", "Executable not found"]:
|
||||||
|
results["skipped"] += 1
|
||||||
|
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
|
||||||
|
# ... (same cleanup logic) ...
|
||||||
|
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
|
||||||
|
# ... (summary printing logic remains the same) ...
|
||||||
|
# Note: Memory summary section only appears if MEASURE_MEMORY is True at the end.
|
||||||
|
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
|
||||||
|
if test_durations:
|
||||||
|
# ... (same calculation and printing) ...
|
||||||
|
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
|
||||||
|
if MEASURE_MEMORY: # Check final flag state
|
||||||
|
if test_memory_usages:
|
||||||
|
# ... (same calculation and printing) ...
|
||||||
|
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
|
||||||
|
if failed_tests:
|
||||||
|
print_color("\n--- Failed Test Cases ---", COLOR_RED)
|
||||||
|
for test in failed_tests:
|
||||||
|
print(f" - {test}")
|
||||||
|
sys.exit(1)
|
||||||
|
# ... (rest of exit logic remains the same) ...
|
||||||
|
elif results['passed'] == 0 and results['skipped'] == total_defined:
|
||||||
|
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:
|
||||||
|
print_color("\nNo tests passed.", COLOR_YELLOW)
|
||||||
|
sys.exit(1 if results['failed'] > 0 else 0)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Lab 13 — Multiple Inheritance & Exceptions
|
# Lab 11 — Multiple Inheritance & Exceptions
|
||||||
|
|
||||||
For this lab you will build a class inheritance structure to match the hierarchy of classic geometric shapes.
|
For this lab you will build a class inheritance structure to match the hierarchy of classic geometric shapes.
|
||||||
The finished program will read lists of 2D point coordinates from a file and determine the shape described
|
The finished program will read lists of 2D point coordinates from a file and determine the shape described
|
||||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -166,6 +166,8 @@ Preorder Traversal using Morris Traversal:
|
|||||||
1 2 4 5 6 7 3 8 9
|
1 2 4 5 6 7 3 8 9
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Play this [animation](https://jidongxiao.github.io/CSCI1200-DataStructures/animations/trees/morris/morris_pre_order.html) to understand how this works.
|
||||||
|
|
||||||
## 21.6 Morris Traversal - Post Order
|
## 21.6 Morris Traversal - Post Order
|
||||||
|
|
||||||
Post order is different, and we need to write some helper functions here.
|
Post order is different, and we need to write some helper functions here.
|
||||||
@@ -238,6 +240,8 @@ Postorder Traversal using Morris Traversal:
|
|||||||
4 6 7 5 2 9 8 3 1
|
4 6 7 5 2 9 8 3 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Play this [animation](https://jidongxiao.github.io/CSCI1200-DataStructures/animations/trees/morris/morrisPostOrder.html) to understand how this works.
|
||||||
|
|
||||||
## Time and Space Complexity in Morris Traversal (in-order, pre-order, post-order)
|
## Time and Space Complexity in Morris Traversal (in-order, pre-order, post-order)
|
||||||
|
|
||||||
- Time Complexity: O(N) (each node is visited at most twice)
|
- Time Complexity: O(N) (each node is visited at most twice)
|
||||||
|
|||||||
@@ -222,5 +222,6 @@ In addition to the above solution, there are also other variations of the heap s
|
|||||||
- [Leetcode problem 912: Sort an Array](https://leetcode.com/problems/sort-an-array/).
|
- [Leetcode problem 912: Sort an Array](https://leetcode.com/problems/sort-an-array/).
|
||||||
- Solution: [p912_heapsort_array.cpp](../../leetcode/p912_heapsort_array.cpp).
|
- Solution: [p912_heapsort_array.cpp](../../leetcode/p912_heapsort_array.cpp).
|
||||||
- Solution 2: - max heap: [p912_heapsort_array_heapify.cpp](../../leetcode/p912_heapsort_array_heapify.cpp).
|
- Solution 2: - max heap: [p912_heapsort_array_heapify.cpp](../../leetcode/p912_heapsort_array_heapify.cpp).
|
||||||
|
- Play this [animation](https://jidongxiao.github.io/CSCI1200-DataStructures/animations/heap/sort/index.html) to see how this sort works.
|
||||||
- Solution 3: - min heap: [p912_heapsort_array_min_heap.cpp](../../leetcode/p912_heapsort_array_min_heap.cpp).
|
- Solution 3: - min heap: [p912_heapsort_array_min_heap.cpp](../../leetcode/p912_heapsort_array_min_heap.cpp).
|
||||||
- Solution 4: - min heap, with functor: [p912_heapsort_array_functor.cpp](../../leetcode/p912_heapsort_array_functor.cpp).
|
- Solution 4: - min heap, with functor: [p912_heapsort_array_functor.cpp](../../leetcode/p912_heapsort_array_functor.cpp).
|
||||||
|
|||||||
299
lectures/25_inheritance/README.md
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
# Lecture 25 --- C++ Inheritance and Polymorphism
|
||||||
|
|
||||||
|
## 25.1 C++ Inheritance
|
||||||
|
|
||||||
|
Inheritance is a fundamental concept in object-oriented programming (OOP) that allows a class (called a derived or child class) to acquire properties and behaviors (methods) from another class (called a base or parent class). This promotes code reusability and establishes a hierarchical relationship between classes.
|
||||||
|
|
||||||
|
## 25.2 Key Concepts
|
||||||
|
|
||||||
|
- Base Class: The class whose properties and methods are inherited.
|
||||||
|
|
||||||
|
- Derived Class: The class that inherits from the base class.
|
||||||
|
|
||||||
|
- Access Specifiers: Determine the accessibility of base class members in the derived class.
|
||||||
|
|
||||||
|
## 25.3 Types of Inheritance
|
||||||
|
|
||||||
|
### 25.3.1 Single Inheritance
|
||||||
|
|
||||||
|
A derived class inherits from one base class.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base {
|
||||||
|
// Base class members
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
// Derived class members
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 25.3.2 Multiple Inheritance
|
||||||
|
|
||||||
|
A derived class inherits from more than one base class.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base1 {
|
||||||
|
// Base1 members
|
||||||
|
};
|
||||||
|
|
||||||
|
class Base2 {
|
||||||
|
// Base2 members
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base1, public Base2 {
|
||||||
|
// Derived class members
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 25.3.3 Multilevel Inheritance
|
||||||
|
|
||||||
|
A class is derived from a class which is also derived from another class.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base {
|
||||||
|
// Base class members
|
||||||
|
};
|
||||||
|
|
||||||
|
class Intermediate : public Base {
|
||||||
|
// Intermediate class members
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Intermediate {
|
||||||
|
// Derived class members
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 25.3.4 Hierarchical Inheritance
|
||||||
|
|
||||||
|
Multiple classes are derived from a single base class.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base {
|
||||||
|
// Base class members
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived1 : public Base {
|
||||||
|
// Derived1 members
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived2 : public Base {
|
||||||
|
// Derived2 members
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 25.4 Access Specifiers in Inheritance: public, protected, and private
|
||||||
|
|
||||||
|
The access specifier used during inheritance affects the accessibility of base class members in the derived class. In C++, the access specifier used during inheritance (public, protected, or private) determines how the base class's members are accessible in the derived class and to external code. Understanding these distinctions is crucial for designing class hierarchies that enforce appropriate encapsulation and access control.
|
||||||
|
|
||||||
|
| Inheritance Type | Base Class `public` Members in Derived | Base Class `protected` Members in Derived | Base Class `private` Members in Derived |
|
||||||
|
|------------------|----------------------------------------|-------------------------------------------|-----------------------------------------|
|
||||||
|
| `public` | `public` | `protected` | Not accessible |
|
||||||
|
| `protected` | `protected` | `protected` | Not accessible |
|
||||||
|
| `private` | `private` | `private` | Not accessible |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
int publicVar;
|
||||||
|
protected:
|
||||||
|
int protectedVar;
|
||||||
|
private:
|
||||||
|
int privateVar;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PublicDerived : public Base {
|
||||||
|
// publicVar is public
|
||||||
|
// protectedVar is protected
|
||||||
|
// privateVar is not accessible
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProtectedDerived : protected Base {
|
||||||
|
// publicVar is protected
|
||||||
|
// protectedVar is protected
|
||||||
|
// privateVar is not accessible
|
||||||
|
};
|
||||||
|
|
||||||
|
class PrivateDerived : private Base {
|
||||||
|
// publicVar is private
|
||||||
|
// protectedVar is private
|
||||||
|
// privateVar is not accessible
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 25.4.1 Default Inheritance:
|
||||||
|
|
||||||
|
If no access specifier is provided:
|
||||||
|
|
||||||
|
- For class, inheritance is private by default.
|
||||||
|
|
||||||
|
- For struct, inheritance is public by default.
|
||||||
|
|
||||||
|
### 25.4.2 Access to Base Class Members:
|
||||||
|
|
||||||
|
private members of the base class are never accessible directly in the derived class, regardless of the inheritance type.
|
||||||
|
|
||||||
|
## 25.5 Constructors and Destructors
|
||||||
|
|
||||||
|
- Constructor Invocation: When a derived class object is created, the base class constructor is invoked first, followed by the derived class constructor.
|
||||||
|
|
||||||
|
- Destructor Invocation: When a derived class object is destroyed, the derived class destructor is invoked first, followed by the base class destructor.
|
||||||
|
|
||||||
|
- Explicit Constructor Call: If the base class constructor requires parameters, the derived class constructor must explicitly call it using an initializer list.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
Base(int x) {
|
||||||
|
// Constructor implementation
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
Derived(int x) : Base(x) {
|
||||||
|
// Derived class constructor implementation
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Pitfall:
|
||||||
|
|
||||||
|
- Member initializer lists play an important role in inheritance in C++.
|
||||||
|
|
||||||
|
- If you try to initialize the base class inside the constructor body (not the initializer list), it won’t work — the base class has already been default-constructed by that point! This is way, for the following two programs:
|
||||||
|
|
||||||
|
[program1](constructor_test1.cpp) will compile; but [program2](constructor_test2.cpp) won't compile.
|
||||||
|
|
||||||
|
## 25.6 Name Hiding
|
||||||
|
|
||||||
|
In C++, when a derived class defines a member function with the same name as one in its base class, the derived class's function hides all base class functions with that name, regardless of their signatures. This behavior is known as name hiding.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class A {
|
||||||
|
public:
|
||||||
|
void func(int x) {
|
||||||
|
std::cout << "A::func(int): " << x << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class B : public A {
|
||||||
|
public:
|
||||||
|
void func(double y) {
|
||||||
|
std::cout << "B::func(double): " << y << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
B b;
|
||||||
|
b.func(10); // Calls B::func(double)
|
||||||
|
b.func(10.5); // Calls B::func(double)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This above program [name_hiding.cpp](name_hiding.cpp) prints:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ g++ name_hiding.cpp
|
||||||
|
$ ./a.out
|
||||||
|
B::func(double): 10
|
||||||
|
B::func(double): 10.5
|
||||||
|
```
|
||||||
|
|
||||||
|
## 25.6 Example Programs:
|
||||||
|
|
||||||
|
We develop this student_test.cpp program.
|
||||||
|
|
||||||
|
### version 1
|
||||||
|
|
||||||
|
[students/student_test1.cpp](students/student_test1.cpp) In this version, there is only one class: class Human
|
||||||
|
|
||||||
|
### version 2
|
||||||
|
|
||||||
|
[students/student_test2.cpp](students/student_test2.cpp) In this version, there is the base class - Human, and the child class - Student, but the child class doesn't have its own member variables, and its only member function is the constructor.
|
||||||
|
|
||||||
|
### version 3
|
||||||
|
|
||||||
|
[students/student_test3.cpp](students/student_test3.cpp) In this version, the child class has its own member functions introduce() and sleep(); and because these functions need to access the parent class's member variables, so we changed the member variables from private to protected.
|
||||||
|
|
||||||
|
### version 4
|
||||||
|
|
||||||
|
[students/student_test4.cpp](students/student_test4.cpp) In this version, we added the destructor to both the child class and the parent class. This program shows that when the child class object is destroyed, first its own destructor gets called, and then its parent destructor gets called.
|
||||||
|
|
||||||
|
### version 5
|
||||||
|
|
||||||
|
[students/student_test5.cpp](students/student_test5.cpp) In this version, we added the CSStudent class, and that introduces multilevel inheritance in the program.
|
||||||
|
|
||||||
|
## 25.7 What will be printed when running this program?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class A {
|
||||||
|
public:
|
||||||
|
A() {
|
||||||
|
std::cout << "A";
|
||||||
|
}
|
||||||
|
~A() {
|
||||||
|
std::cout << "A";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class B : public A {
|
||||||
|
public:
|
||||||
|
B() {
|
||||||
|
std::cout << "B";
|
||||||
|
}
|
||||||
|
~B() {
|
||||||
|
std::cout << "B";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
{
|
||||||
|
A a;
|
||||||
|
B b;
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 25.8 What will be printed when running this program?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class A {
|
||||||
|
public:
|
||||||
|
A() {
|
||||||
|
std::cout << "A";
|
||||||
|
}
|
||||||
|
~A() {
|
||||||
|
std::cout << "A";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class B : public A {
|
||||||
|
public:
|
||||||
|
B() {
|
||||||
|
std::cout << "B";
|
||||||
|
}
|
||||||
|
~B() {
|
||||||
|
std::cout << "B";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
{
|
||||||
|
B* p = new B;
|
||||||
|
B b;
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
18
lectures/25_inheritance/constructor_test1.cpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
Base(int x) {
|
||||||
|
std::cout << "Base constructor: " << x << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
Derived(int x) : Base(x) { // member initializer list calls Base constructor
|
||||||
|
std::cout << "Derived constructor" << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
}
|
||||||
19
lectures/25_inheritance/constructor_test2.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
Base(int x) {
|
||||||
|
std::cout << "Base constructor: " << x << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
Derived(int x) { // member initializer list calls Base constructor
|
||||||
|
Base(x);
|
||||||
|
std::cout << "Derived constructor" << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
}
|
||||||
31
lectures/25_inheritance/exercises/exercise1.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class A {
|
||||||
|
public:
|
||||||
|
A() {
|
||||||
|
std::cout << "A";
|
||||||
|
}
|
||||||
|
~A() {
|
||||||
|
std::cout << "A";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class B : public A {
|
||||||
|
public:
|
||||||
|
B() {
|
||||||
|
std::cout << "B";
|
||||||
|
}
|
||||||
|
~B() {
|
||||||
|
std::cout << "B";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
{
|
||||||
|
A a;
|
||||||
|
B b;
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
32
lectures/25_inheritance/exercises/exercise2.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class A {
|
||||||
|
public:
|
||||||
|
A() {
|
||||||
|
std::cout << "A";
|
||||||
|
}
|
||||||
|
~A() {
|
||||||
|
std::cout << "A";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class B : public A {
|
||||||
|
public:
|
||||||
|
B() {
|
||||||
|
std::cout << "B";
|
||||||
|
}
|
||||||
|
~B() {
|
||||||
|
std::cout << "B";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
{
|
||||||
|
B* p = new B;
|
||||||
|
B b;
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
22
lectures/25_inheritance/name_hiding.cpp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class A {
|
||||||
|
public:
|
||||||
|
void func(int x) {
|
||||||
|
std::cout << "A::func(int): " << x << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class B : public A {
|
||||||
|
public:
|
||||||
|
void func(double y) {
|
||||||
|
std::cout << "B::func(double): " << y << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
B b;
|
||||||
|
b.func(10); // Calls B::func(double)
|
||||||
|
b.func(10.5); // Calls B::func(double)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
35
lectures/25_inheritance/students/student_test1.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Human {
|
||||||
|
private:
|
||||||
|
std::string name;
|
||||||
|
int age;
|
||||||
|
int sleep_hours;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Human(std::string n, int a, int s) : name(n), age(a), sleep_hours(s) {}
|
||||||
|
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "Hello, I am " << name << ", and I am " << age << " years old.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is " << age << " years old who sleeps " << sleep_hours << " hours a night.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Creating instances of each class with member variables
|
||||||
|
Human h("Alice", 30, 8);
|
||||||
|
|
||||||
|
// Introducing Humans
|
||||||
|
std::cout << "--- Human introducing ---\n";
|
||||||
|
h.introduce(); // Output: Hello, I am Alice, and I am 30 years old.
|
||||||
|
|
||||||
|
// Showing sleep behavior
|
||||||
|
std::cout << "--- Human sleep ---\n";
|
||||||
|
h.sleep(); // Output: Alice is 30 years old and sleeps 8 hours a night.
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
54
lectures/25_inheritance/students/student_test2.cpp
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Human {
|
||||||
|
private:
|
||||||
|
std::string name;
|
||||||
|
int age;
|
||||||
|
int sleep_hours;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Human(std::string n, int a, int s) : name(n), age(a), sleep_hours(s) {}
|
||||||
|
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "Hello, I am " << name << ", and I am " << age << " years old.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is " << age << " years old who sleeps " << sleep_hours << " hours a night.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Student : public Human {
|
||||||
|
public:
|
||||||
|
/* in C++, when a derived class inherits from a base class,
|
||||||
|
* the base class's constructor must be called to initialize its members.
|
||||||
|
* This is because the base class may contain private or protected members
|
||||||
|
* that are not directly accessible by the derived class.
|
||||||
|
* Therefore, the derived class relies on the base class's constructor to properly initialize these members. */
|
||||||
|
|
||||||
|
Student(std::string n, int a, int s) : Human(n, a, s) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Creating instances of each class with member variables
|
||||||
|
Human h("Alice", 30, 8);
|
||||||
|
Student s("Bob", 20, 5);
|
||||||
|
|
||||||
|
// Introducing Humans
|
||||||
|
std::cout << "--- Human introducing ---\n";
|
||||||
|
h.introduce(); // Output: Hello, I am Alice, and I am 30 years old.
|
||||||
|
|
||||||
|
std::cout << "--- Student introducing ---\n";
|
||||||
|
s.introduce(); // Output: I am a student. My name is Bob, and I am 20 years old.
|
||||||
|
|
||||||
|
// Showing sleep behavior
|
||||||
|
std::cout << "--- Human sleep ---\n";
|
||||||
|
h.sleep(); // Output: Alice is 30 years old and sleeps 8 hours a night.
|
||||||
|
|
||||||
|
std::cout << "--- Student sleep ---\n";
|
||||||
|
s.sleep(); // Output: Bob is a student, and they sleep 5 hours a night.
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
58
lectures/25_inheritance/students/student_test3.cpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Human {
|
||||||
|
// change this to protect so that these variables can be accessed by the derived class.
|
||||||
|
protected:
|
||||||
|
std::string name;
|
||||||
|
int age;
|
||||||
|
int sleep_hours;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Human(std::string n, int a, int s) : name(n), age(a), sleep_hours(s) {}
|
||||||
|
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "Hello, I am " << name << ", and I am " << age << " years old.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is " << age << " years old who sleeps " << sleep_hours << " hours a night.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Student : public Human {
|
||||||
|
public:
|
||||||
|
Student(std::string n, int a, int s) : Human(n, a, s) {}
|
||||||
|
|
||||||
|
// note that the Student class never defines the name and age variables, but we can use them here, because of inheritance.
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "Hello, I am " << name << ", and I am " << age << " years old. I’m majoring in 'How did I get here?' with a minor in 'It sounded easier when I signed up.'\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is a college student who sleeps " << sleep_hours << " hours a night, and sleep 2 hours during boring lectures.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Creating instances of each class with member variables
|
||||||
|
Human h("Alice", 30, 8);
|
||||||
|
Student s("Bob", 20, 5);
|
||||||
|
|
||||||
|
// Introducing Humans
|
||||||
|
std::cout << "--- Human introducing ---\n";
|
||||||
|
h.introduce(); // Output: Hello, I am Alice, and I am 30 years old.
|
||||||
|
|
||||||
|
std::cout << "--- Student introducing ---\n";
|
||||||
|
s.introduce(); // Output: I am a student. My name is Bob, and I am 20 years old.
|
||||||
|
|
||||||
|
// Showing sleep behavior
|
||||||
|
std::cout << "--- Human sleep ---\n";
|
||||||
|
h.sleep(); // Output: Alice is 30 years old and sleeps 8 hours a night.
|
||||||
|
|
||||||
|
std::cout << "--- Student sleep ---\n";
|
||||||
|
s.sleep(); // Output: Bob is a student, and they sleep 5 hours a night.
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
63
lectures/25_inheritance/students/student_test4.cpp
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Human {
|
||||||
|
// change this to protect so that these variables can be accessed by the derived class.
|
||||||
|
protected:
|
||||||
|
std::string name;
|
||||||
|
int age;
|
||||||
|
int sleep_hours;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Human(std::string n, int a, int s) : name(n), age(a), sleep_hours(s) {}
|
||||||
|
~Human(){
|
||||||
|
std::cout << "Human Destructor" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "Hello, I am " << name << ", and I am " << age << " years old.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is " << age << " years old who sleeps " << sleep_hours << " hours a night.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Student : public Human {
|
||||||
|
public:
|
||||||
|
Student(std::string n, int a, int s) : Human(n, a, s) {}
|
||||||
|
~Student(){
|
||||||
|
std::cout << "Student Destructor" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "Hello, I am " << name << ", and I am " << age << " years old. I’m majoring in 'How did I get here?' with a minor in 'It sounded easier when I signed up.'\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is a college student who sleeps " << sleep_hours << " hours a night, and sleep 2 hours during boring lectures.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Creating instances of each class with member variables
|
||||||
|
Human h("Alice", 30, 8);
|
||||||
|
Student s("Bob", 20, 5);
|
||||||
|
|
||||||
|
// Introducing Humans
|
||||||
|
std::cout << "--- Human introducing ---\n";
|
||||||
|
h.introduce(); // Output: Hello, I am Alice, and I am 30 years old.
|
||||||
|
|
||||||
|
std::cout << "--- Student introducing ---\n";
|
||||||
|
s.introduce(); // Output: I am a student. My name is Bob, and I am 20 years old.
|
||||||
|
|
||||||
|
// Showing sleep behavior
|
||||||
|
std::cout << "--- Human sleep ---\n";
|
||||||
|
h.sleep(); // Output: Alice is 30 years old and sleeps 8 hours a night.
|
||||||
|
|
||||||
|
std::cout << "--- Student sleep ---\n";
|
||||||
|
s.sleep(); // Output: Bob is a student, and they sleep 5 hours a night.
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
88
lectures/25_inheritance/students/student_test5.cpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Human {
|
||||||
|
// change this to protect so that these variables can be accessed by the derived class.
|
||||||
|
protected:
|
||||||
|
std::string name;
|
||||||
|
int age;
|
||||||
|
int sleep_hours;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Human(std::string n, int a, int s) : name(n), age(a), sleep_hours(s) {}
|
||||||
|
~Human(){
|
||||||
|
std::cout << "Human Destructor" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "Hello, I am " << name << ", and I am " << age << " years old.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is " << age << " years old who sleeps " << sleep_hours << " hours a night.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Student : public Human {
|
||||||
|
public:
|
||||||
|
Student(std::string n, int a, int s) : Human(n, a, s) {}
|
||||||
|
~Student(){
|
||||||
|
std::cout << "Student Destructor" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "Hello, I am " << name << ", and I am " << age << " years old. I’m majoring in 'How did I get here?' with a minor in 'It sounded easier when I signed up.'\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is a college student who sleeps " << sleep_hours << " hours a night, and sleep 2 hours during boring lectures.\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CSStudent : public Student {
|
||||||
|
public:
|
||||||
|
// note that CSStudent constructor needs to call Student constructor.
|
||||||
|
CSStudent(std::string n, int a, int s) : Student(n, a, s) {}
|
||||||
|
~CSStudent(){
|
||||||
|
std::cout << "CS Student Destructor" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void introduce() {
|
||||||
|
std::cout << "I am a CS student. My name is " << name << ", and I am " << age << " years old.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep() {
|
||||||
|
std::cout << name << " is a CS student who sleeps " << sleep_hours << " hours a night. "
|
||||||
|
<< "Or maybe, just a few minutes if the code compiles!\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Creating instances of each class with member variables
|
||||||
|
Human h("Alice", 30, 8);
|
||||||
|
Student s("Bob", 20, 5);
|
||||||
|
CSStudent cs("Charlie", 22, 2);
|
||||||
|
|
||||||
|
// Introducing Humans
|
||||||
|
std::cout << "--- Human introducing ---\n";
|
||||||
|
h.introduce(); // Output: Hello, I am Alice, and I am 30 years old.
|
||||||
|
|
||||||
|
std::cout << "--- Student introducing ---\n";
|
||||||
|
s.introduce(); // Output: I am a student. My name is Bob, and I am 20 years old.
|
||||||
|
|
||||||
|
std::cout << "--- CS Student introducing ---\n";
|
||||||
|
cs.introduce(); // Output: I am a CS student. My name is Charlie, and I am 22 years old.
|
||||||
|
|
||||||
|
// Showing sleep behavior
|
||||||
|
std::cout << "--- Human sleep ---\n";
|
||||||
|
h.sleep(); // Output: Alice is 30 years old and sleeps 8 hours a night.
|
||||||
|
|
||||||
|
std::cout << "--- Student sleep ---\n";
|
||||||
|
s.sleep(); // Output: Bob is a student, and they sleep 5 hours a night.
|
||||||
|
|
||||||
|
std::cout << "--- CS Student introducing ---\n";
|
||||||
|
cs.sleep(); // Output: I am a CS student. My name is Charlie, and I am 22 years old.
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
# Lecture 26 --- C++ Inheritance and Polymorphism
|
|
||||||
|
|
||||||
## Today’s Lecture
|
|
||||||
|
|
||||||
- Inheritance is a relationship among classes. Examples: bank accounts, polygons, stack & list
|
|
||||||
- Basic mechanisms of inheritance
|
|
||||||
- Types of inheritance
|
|
||||||
- Is-A, Has-A, As-A relationships among classes.
|
|
||||||
- Polymorphism
|
|
||||||
|
|
||||||
## 26.1 Motivating Example: Bank Accounts
|
|
||||||
|
|
||||||
- Consider different types of bank accounts:
|
|
||||||
– Savings accounts
|
|
||||||
– Checking accounts
|
|
||||||
– Time withdrawal accounts (like savings accounts, except that only the interest can be withdrawn)
|
|
||||||
- If you were designing C++ classes to represent each of these, what member functions might be repeated among the different classes? What member functions would be unique to a given class?
|
|
||||||
- To avoid repeating common member functions and member variables, we will create a class hierarchy, where the common members are placed in a base class and specialized members are placed in derived classes.
|
|
||||||
|
|
||||||
## 26.2 Accounts Hierarchy
|
|
||||||
|
|
||||||
- Account is the base class of the hierarchy.
|
|
||||||
- SavingsAccount is a derived class from Account. SavingsAccount has inherited member variables & functions
|
|
||||||
and ordinarily-defined member variables & functions.
|
|
||||||
- The member variable balance in base class Account is protected, which means:
|
|
||||||
– balance is NOT publicly accessible outside the class, but it is accessible in the derived classes.
|
|
||||||
– if balance was declared as private, then SavingsAccount member functions could not access it.
|
|
||||||
- When using objects of type SavingsAccount, the inherited and derived members are treated exactly the same
|
|
||||||
and are not distinguishable.
|
|
||||||
- CheckingAccount is also a derived class from base class Account.
|
|
||||||
- TimeAccount is derived from SavingsAccount. SavingsAccount is its base class and Account is its indirect base class
|
|
||||||
|
|
||||||
## 26.3 Exercise: Draw the Accounts Class Hierarchy
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <iostream>
|
|
||||||
// Note we've inlined all the functions (even though some are > 1 line of code)
|
|
||||||
class Account {
|
|
||||||
public:
|
|
||||||
Account(double bal = 0.0) : balance(bal) {}
|
|
||||||
void deposit(double amt) { balance += amt; }
|
|
||||||
double get_balance() const { return balance; }
|
|
||||||
protected:
|
|
||||||
double balance; // account balance
|
|
||||||
};
|
|
||||||
class SavingsAccount : public Account {
|
|
||||||
public:
|
|
||||||
SavingsAccount(double bal = 0.0, double pct = 5.0) : Account(bal), rate(pct/100.0) {}
|
|
||||||
double compound() { // computes and deposits interest
|
|
||||||
double interest = balance * rate;
|
|
||||||
balance += interest;
|
|
||||||
return interest;
|
|
||||||
}
|
|
||||||
double withdraw(double amt) { // if overdraft ==> return 0, else return amount
|
|
||||||
if (amt > balance) {
|
|
||||||
return 0.0;
|
|
||||||
}else {
|
|
||||||
balance -= amt;
|
|
||||||
return amt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
double rate; // periodic interest rate
|
|
||||||
};
|
|
||||||
class CheckingAccount : public Account {
|
|
||||||
public:
|
|
||||||
CheckingAccount(double bal = 0.0, double lim = 500.0, double chg = 0.5) : Account(bal), limit(lim), charge(chg) {}
|
|
||||||
double cash_check(double amt) {
|
|
||||||
assert (amt > 0);
|
|
||||||
if (balance < limit && (amt + charge <= balance)) {
|
|
||||||
balance -= amt + charge;
|
|
||||||
return amt + charge;
|
|
||||||
} else if (balance >= limit && amt <= balance) {
|
|
||||||
balance -= amt;
|
|
||||||
return amt;
|
|
||||||
} else {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
double limit; // lower limit for free checking
|
|
||||||
double charge; // per check charge
|
|
||||||
};
|
|
||||||
class TimeAccount : public SavingsAccount {
|
|
||||||
public:
|
|
||||||
TimeAccount(double bal = 0.0, double pct = 5.0) : SavingsAccount(bal, pct), funds_avail(0.0) {}
|
|
||||||
// redefines 2 member functions from SavingsAccount
|
|
||||||
double compound() {
|
|
||||||
double interest = SavingsAccount::compound();
|
|
||||||
funds_avail += interest;
|
|
||||||
return interest;
|
|
||||||
}
|
|
||||||
double withdraw(double amt) {
|
|
||||||
if (amt <= funds_avail) {
|
|
||||||
funds_avail -= amt;
|
|
||||||
balance -= amt;
|
|
||||||
return amt;
|
|
||||||
} else {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
double get_avail() const { return funds_avail; };
|
|
||||||
protected:
|
|
||||||
double funds_avail; // amount available for withdrawal
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
Account a(100); //<---one balance member, not related to c1
|
|
||||||
CheckingAccount c1(100, 366, 0.4); //c1 has it's own CheckingAccount + Account objects <---one balance member
|
|
||||||
```
|
|
||||||
|
|
||||||
## 26.4 Constructors and Destructors
|
|
||||||
|
|
||||||
- Constructors of a derived class call the base class constructor immediately, before doing ANYTHING else.
|
|
||||||
The only thing you can control is which constructor is called and what the arguments will be. Thus when
|
|
||||||
a TimeAccount is created 3 constructors are called: the Account constructor, then the SavingsAccount
|
|
||||||
constructor, and then finally the TimeAccount constructor.
|
|
||||||
- The reverse is true for destructors: derived class destructors do their jobs first and then base class destructors
|
|
||||||
are called at the end, automatically. Note: destructors for classes which have derived classes must be marked
|
|
||||||
virtual for this chain of calls to happen.
|
|
||||||
|
|
||||||
## 26.5 Overriding Member Functions in Derived Classes
|
|
||||||
|
|
||||||
- A derived class can redefine member functions in the base class. The function prototype must be identical, not even the use of const can be different.
|
|
||||||
- For example, see TimeAccount::compound and TimeAccount::withdraw.
|
|
||||||
- Once a function is redefined it is not possible to call the base class function, unless it is explicitly called. As an example, the call to SavingsAccount::compound inside of TimeAccount::compound.
|
|
||||||
|
|
||||||
## 26.6 Public, Private and Protected Inheritance
|
|
||||||
|
|
||||||
- Notice the line
|
|
||||||
```cpp
|
|
||||||
class Savings_Account : public Account {
|
|
||||||
```
|
|
||||||
This specifies that the member functions and variables from Account do not change their public, protected or private status in SavingsAccount. This is called public inheritance.
|
|
||||||
- protected and private inheritance are other options:
|
|
||||||
|
|
||||||
– With protected inheritance, public members becomes protected and other members are unchanged
|
|
||||||
|
|
||||||
– With private inheritance, all members become private.
|
|
||||||
|
|
||||||
## 26.7 Stack Inheriting from List
|
|
||||||
|
|
||||||
- For another example of inheritance, let’s re-implement the stack class as a derived class of std::list:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
template <class T>
|
|
||||||
class stack : private std::list<T> {
|
|
||||||
public:
|
|
||||||
stack() {}
|
|
||||||
stack(stack<T> const& other) : std::list<T>(other) {}
|
|
||||||
~stack() {}
|
|
||||||
void push(T const& value) { this->push_back(value); }
|
|
||||||
void pop() { this->pop_back(); }
|
|
||||||
T const& top() const { return this->back(); }
|
|
||||||
int size() { return std::list<T>::size(); }
|
|
||||||
bool empty() { return std::list<T>::empty(); }
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
- Private inheritance hides the std::list<T> member functions from the outside world. However, these member functions are still available to the member functions of the stack<T> class.
|
|
||||||
- Note: no member variables are defined — the only member variables needed are in the list class.
|
|
||||||
- When the stack member function uses the same name as the base class (list) member function, the name of the base class followed by :: must be provided to indicate that the base class member function is to be used.
|
|
||||||
- The copy constructor just uses the copy constructor of the base class, without any special designation because the stack object is a list object as well.
|
|
||||||
|
|
||||||
## 26.8 Is-A, Has-A, As-A Relationships Among Classes
|
|
||||||
|
|
||||||
- When trying to determine the relationship between (hypothetical) classes C1 and C2, try to think of a logical relationship between them that can be written:
|
|
||||||
– C1 is a C2,
|
|
||||||
– C1 has a C2, or
|
|
||||||
– C1 is implemented as a C2
|
|
||||||
- If writing “C1 is-a C2” is best, for example: “a savings account is an account”, then C1 should be a derived class (a subclass) of C2.
|
|
||||||
- If writing “C1 has-a C2” is best, for example: “a cylinder has a circle as its base”, then class C1 should have a member variable of type C2.
|
|
||||||
- In the case of “C1 is implemented as-a C2”, for example: “the stack is implemented as a list”, then C1 should be derived from C2, but with private inheritance. This is by far the least common case!
|
|
||||||
|
|
||||||
## 26.9 Exercise: 2D Geometric Primitives
|
|
||||||
|
|
||||||
Create a class hierarchy of geometric objects, such as: triangle, isosceles triangle, right triangle, quadrilateral, square,
|
|
||||||
rhombus, kite, trapezoid, circle, ellipse, etc. How should this hierarchy be arranged? What member variables and
|
|
||||||
member functions should be in each class?
|
|
||||||
|
|
||||||
## 26.10 Multiple Inheritance
|
|
||||||
|
|
||||||
- When sketching a class hierarchy for geometric objects, you may have wanted to specify relationships that were more complex... in particular some objects may wish to inherit from more than one base class.
|
|
||||||
- This is called multiple inheritance and can make many implementation details significantly more hairy. Different programming languages offer different variations of multiple inheritance.
|
|
||||||
- See [example 1](multiple_inheritance1.cpp) and [example 2](multiple_inheritance2.cpp).
|
|
||||||
|
|
||||||
- And see [example 3](multiple_level_inheritance.cpp) for a multiple level inheritance example.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 26.11 Introduction to Polymorphism
|
|
||||||
|
|
||||||
- Let’s consider a small class hierarchy version of polygonal objects:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
class Polygon {
|
|
||||||
public:
|
|
||||||
Polygon() {}
|
|
||||||
virtual ~Polygon() {}
|
|
||||||
int NumVerts() { return verts.size(); }
|
|
||||||
virtual double Area() = 0;
|
|
||||||
virtual bool IsSquare() { return false; }
|
|
||||||
protected:
|
|
||||||
vector<Point> verts;
|
|
||||||
};
|
|
||||||
class Triangle : public Polygon {
|
|
||||||
public:
|
|
||||||
Triangle(Point pts[3]) {
|
|
||||||
for (int i = 0; i < 3; i++) verts.push_back(pts[i]); }
|
|
||||||
double Area();
|
|
||||||
};
|
|
||||||
class Quadrilateral : public Polygon {
|
|
||||||
public:
|
|
||||||
Quadrilateral(Point pts[4]) {
|
|
||||||
for (int i = 0; i < 4; i++) verts.push_back(pts[i]); }
|
|
||||||
double Area();
|
|
||||||
double LongerDiagonal();
|
|
||||||
bool IsSquare() { return (SidesEqual() && AnglesEqual()); }
|
|
||||||
private:
|
|
||||||
bool SidesEqual();
|
|
||||||
bool AnglesEqual();
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
- Functions that are common, at least have a common interface, are in Polygon.
|
|
||||||
- Some of these functions are marked virtual, which means that when they are redefined by a derived class, this new definition will be used, even for pointers to base class objects.
|
|
||||||
- Some of these virtual functions, those whose declarations are followed by = 0 are pure virtual, which means
|
|
||||||
they must be redefined in a derived class.
|
|
||||||
– Any class that has pure virtual functions is called “abstract”.
|
|
||||||
– Objects of abstract types may not be created — only pointers to these objects may be created.
|
|
||||||
- Functions that are specific to a particular object type are declared in the derived class prototype.
|
|
||||||
|
|
||||||
## 26.12 A Polymorphic List of Polygon Objects
|
|
||||||
|
|
||||||
- Now instead of two separate lists of polygon objects, we can create one “polymorphic” list:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
std::list<Polygon*> polygons;
|
|
||||||
```
|
|
||||||
|
|
||||||
- Objects are constructed using new and inserted into the list:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
Polygon *p_ptr = new Triangle( .... );
|
|
||||||
polygons.push_back(p_ptr);
|
|
||||||
p_ptr = new Quadrilateral( ... );
|
|
||||||
polygons.push_back(p_ptr);
|
|
||||||
Triangle *t_ptr = new Triangle( .... );
|
|
||||||
polygons.push_back(t_ptr);
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: We’ve used the same pointer variable (p_ptr) to point to objects of two different types.
|
|
||||||
|
|
||||||
## 26.13 Accessing Objects Through a Polymorphic List of Pointers
|
|
||||||
|
|
||||||
- Let’s sum the areas of all the polygons:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
double area = 0;
|
|
||||||
for (std::list<Polygon*>::iterator i = polygons.begin(); i!=polygons.end(); ++i){
|
|
||||||
area += (*i)->Area();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Which Area function is called? If *i points to a Triangle object then the function defined in the Triangle
|
|
||||||
class would be called. If *i points to a Quadrilateral object then Quadrilateral::Area will be called.
|
|
||||||
|
|
||||||
- Here’s code to count the number of squares in the list:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int count = 0;
|
|
||||||
for (std::list<Polygon*>::iterator i = polygons.begin(); i!=polygons.end(); ++i){
|
|
||||||
count += (*i)->IsSquare();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If Polygon::IsSquare had not been declared virtual then the function defined in Polygon would always be
|
|
||||||
called! In general, given a pointer to type T we start at T and look “up” the hierarchy for the closest function
|
|
||||||
definition (this can be done at compile time). If that function has been declared virtual, we will start this
|
|
||||||
search instead at the actual type of the object (this requires additional work at runtime) in case it has been
|
|
||||||
redefined in a derived class of type T.
|
|
||||||
|
|
||||||
- To use a function in Quadrilateral that is not declared in Polygon, you must “cast” the pointer. The pointer
|
|
||||||
*q will be NULL if *i is not a Quadrilateral object.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
for (std::list<Polygon*>::iterator i = polygons.begin(); i!=polygons.end(); ++i) {
|
|
||||||
Quadrilateral *q = dynamic_cast<Quadrilateral*> (*i);
|
|
||||||
if (q) std::cout << "diagonal: " << q->LongerDiagonal() << std::endl;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 26.14 Exercise
|
|
||||||
|
|
||||||
What is the output of the following [program](exercise.cpp)?
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
class Base {
|
|
||||||
public:
|
|
||||||
Base() {}
|
|
||||||
virtual void A() { std::cout << "Base A "; }
|
|
||||||
void B() { std::cout << "Base B "; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class One : public Base {
|
|
||||||
public:
|
|
||||||
One() {}
|
|
||||||
void A() { std::cout << "One A "; }
|
|
||||||
void B() { std::cout << "One B "; }
|
|
||||||
};
|
|
||||||
class Two : public Base {
|
|
||||||
public:
|
|
||||||
Two() {}
|
|
||||||
void A() { std::cout << "Two A "; }
|
|
||||||
void B() { std::cout << "Two B "; }
|
|
||||||
};
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
Base* a[3];
|
|
||||||
a[0] = new Base;
|
|
||||||
a[1] = new One;
|
|
||||||
a[2] = new Two;
|
|
||||||
for (unsigned int i=0; i<3; ++i) {
|
|
||||||
a[i]->A();
|
|
||||||
a[i]->B();
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 26.15 Exercise
|
|
||||||
|
|
||||||
What is the output of the following [program](virtual.cpp)?
|
|
||||||
|
Before Width: | Height: | Size: 398 KiB After Width: | Height: | Size: 398 KiB |
477
lectures/26_inheritance_II/README.md
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
# Lecture 26 --- C++ Inheritance and Polymorphism
|
||||||
|
|
||||||
|
## 26.1 Multiple Inheritance
|
||||||
|
|
||||||
|
- Multiple inheritance allows a class to inherit from more than one base class.
|
||||||
|
|
||||||
|
- The following [program](multiple_inheritance1.cpp) is an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class B
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
B(int b1):b(b1){}
|
||||||
|
protected:
|
||||||
|
int b;
|
||||||
|
};
|
||||||
|
|
||||||
|
class C
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
C(int c1):c(c1){}
|
||||||
|
protected:
|
||||||
|
int c;
|
||||||
|
};
|
||||||
|
|
||||||
|
class D: public B, public C
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
D(int b1, int c1):B(b1),C(c1),d(b1+c1){}
|
||||||
|
|
||||||
|
void print(){
|
||||||
|
std::cout << "d is " << d << std::endl;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
int d;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
D* dOjbect = new D(2,3);
|
||||||
|
dOjbect->print();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 26.2 The Diamond Problem
|
||||||
|
|
||||||
|
- The Diamond Problem occurs in multiple inheritance when two classes inherit from the same base class, and a fourth class inherits from both of those.
|
||||||
|
|
||||||
|
```
|
||||||
|
Human
|
||||||
|
/ \
|
||||||
|
Student Worker
|
||||||
|
\ /
|
||||||
|
CSStudent
|
||||||
|
```
|
||||||
|
|
||||||
|
- Both Student and Worker inherit from Human.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Human {
|
||||||
|
public:
|
||||||
|
void speak();
|
||||||
|
};
|
||||||
|
|
||||||
|
class Student : public Human {};
|
||||||
|
class Worker : public Human {};
|
||||||
|
|
||||||
|
class CSStudent : public Student, public Worker {};
|
||||||
|
```
|
||||||
|
|
||||||
|
- CSStudent inherits from both Student and Worker.
|
||||||
|
|
||||||
|
- Compiler sees two Human base classes.
|
||||||
|
|
||||||
|
- This leads to duplicate data and ambiguous member resolution.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
CSStudent cs;
|
||||||
|
cs.speak(); // ❌ Ambiguous: which Human::speak()?
|
||||||
|
```
|
||||||
|
|
||||||
|
### Solution: Virtual Inheritance
|
||||||
|
|
||||||
|
- Use the virtual keyword when inheriting the common base class.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Student : virtual public Human {};
|
||||||
|
class Worker : virtual public Human {};
|
||||||
|
class CSStudent : public Student, public Worker {};
|
||||||
|
CSStudent cs;
|
||||||
|
cs.speak(); // ✅ No ambiguity
|
||||||
|
```
|
||||||
|
|
||||||
|
- How it works:
|
||||||
|
|
||||||
|
- Compiler ensures only one shared instance of Human.
|
||||||
|
|
||||||
|
- Student and Worker do not create their own copies of Human.
|
||||||
|
|
||||||
|
- Memory layout uses pointers behind the scenes to share the base.
|
||||||
|
|
||||||
|
### Constructor Order with Virtual Inheritance
|
||||||
|
|
||||||
|
When virtual inheritance is involved:
|
||||||
|
|
||||||
|
Most derived class (e.g., CSStudent) is responsible for calling the base (Human) constructor.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Human {
|
||||||
|
public:
|
||||||
|
Human(int age) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Student : virtual public Human {
|
||||||
|
public:
|
||||||
|
Student() : Human(0) {} // ❌ Not allowed to call Human(int) here
|
||||||
|
};
|
||||||
|
|
||||||
|
class CSStudent : public Student {
|
||||||
|
public:
|
||||||
|
CSStudent() : Human(21), Student() {} // ✅ Human constructor called here
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 26.3 Introduction to Polymorphism
|
||||||
|
|
||||||
|
- Polymorphism means "many forms".
|
||||||
|
|
||||||
|
- Polymorphism allows one interface to be used for different data types or classes. In C++, it enables objects to be treated as instances of their base type while still calling derived class methods.
|
||||||
|
|
||||||
|
- A function marked with the virtual keyword in the base class allows derived classes to override it. At runtime, the correct version of the function is called based on the object type.
|
||||||
|
|
||||||
|
- Virtual functions only come into play with pointers or references to the base class.
|
||||||
|
|
||||||
|
### 26.3.1 Case 1: Direct Object (No Need for virtual)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
void show() {
|
||||||
|
std::cout << "Base\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
void show() {
|
||||||
|
std::cout << "Derived\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Derived d;
|
||||||
|
d.show(); // ✅ "Derived" is printed. No virtual needed.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 26.3.2 Case 2: Base Pointer or Reference (Needs virtual)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
virtual void show() {
|
||||||
|
std::cout << "Base\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
void show() override {
|
||||||
|
std::cout << "Derived\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Base* b = new Derived();
|
||||||
|
b->show(); // ✅ "Derived" is printed because `show()` is virtual.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Without virtual, Base::show() would be called, even though b points to a Derived.
|
||||||
|
|
||||||
|
- The override keyword tells the compiler: "I intend to override a virtual function from the base class."
|
||||||
|
|
||||||
|
- You don’t strictly need the override keyword here — the program will still work as expected because Base::show() is declared virtual, and Derived::show() has the same signature. But using override is highly recommended: when override is used, and if you make a mistake, like mismatching the function signature (even by accident), the compiler will catch it.
|
||||||
|
|
||||||
|
### 26.3.3 Virtual Functions in Multi-level Inheritance
|
||||||
|
|
||||||
|
- virtual functions are automatically inherited in C++, even if you don't repeat the virtual keyword in the derived class.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
virtual void greet() {
|
||||||
|
std::cout << "Base greet\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Mid : public Base {
|
||||||
|
public:
|
||||||
|
void greet() { // still virtual, even without saying "virtual"
|
||||||
|
std::cout << "Mid greet\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Mid {
|
||||||
|
public:
|
||||||
|
void greet() override {
|
||||||
|
std::cout << "Derived greet\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Base* obj = new Derived();
|
||||||
|
obj->greet(); // ✅ prints "Derived greet"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 26.4 Exercise
|
||||||
|
|
||||||
|
What is the output of the following [program](exercise.cpp)?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
Base() {}
|
||||||
|
virtual void A() { std::cout << "Base A "; }
|
||||||
|
void B() { std::cout << "Base B "; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class One : public Base {
|
||||||
|
public:
|
||||||
|
One() {}
|
||||||
|
void A() { std::cout << "One A "; }
|
||||||
|
void B() { std::cout << "One B "; }
|
||||||
|
};
|
||||||
|
class Two : public Base {
|
||||||
|
public:
|
||||||
|
Two() {}
|
||||||
|
void A() { std::cout << "Two A "; }
|
||||||
|
void B() { std::cout << "Two B "; }
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Base* a[3];
|
||||||
|
a[0] = new Base;
|
||||||
|
a[1] = new One;
|
||||||
|
a[2] = new Two;
|
||||||
|
for (unsigned int i=0; i<3; ++i) {
|
||||||
|
a[i]->A();
|
||||||
|
a[i]->B();
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 26.5 Exercise
|
||||||
|
|
||||||
|
What is the output of the following [program](virtual.cpp)?
|
||||||
|
|
||||||
|
## 26.6 Memory Usage of Virtual Functions
|
||||||
|
|
||||||
|
Given the following [program](virtual2.cpp), what is the memory size of each class?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Human {
|
||||||
|
};
|
||||||
|
|
||||||
|
class Student {
|
||||||
|
int age;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CollegeStudent {
|
||||||
|
int age;
|
||||||
|
void print(){
|
||||||
|
std::cout << "I am a college student." << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CSStudent {
|
||||||
|
int age;
|
||||||
|
virtual void print(){
|
||||||
|
std::cout << "I am a CS student." << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
std::cout << "memory size of Human class is: " << sizeof(Human) << std::endl;
|
||||||
|
std::cout << "memory size of Student class is: " << sizeof(Student) << std::endl;
|
||||||
|
std::cout << "memory size of College Student class is: " << sizeof(CollegeStudent) << std::endl;
|
||||||
|
std::cout << "memory size of CS Student class is: " << sizeof(CSStudent) << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 26.6.1 Empty Class
|
||||||
|
|
||||||
|
- An empty C++ class takes one byte because the C++ standard requires that each distinct object has a unique address in memory.
|
||||||
|
|
||||||
|
- If an empty class had size 0, then multiple instances of that class could end up having the same memory address, which would break basic assumptions in C++ like this:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Empty {};
|
||||||
|
|
||||||
|
Empty a, b;
|
||||||
|
std::cout << (&a == &b); // This should be false!
|
||||||
|
```
|
||||||
|
|
||||||
|
- To make sure that &a != &b, the compiler gives each object at least one byte of storage, even if the class doesn’t contain any data.
|
||||||
|
|
||||||
|
### 26.6.2 Class CSStudent: Total size breakdown
|
||||||
|
|
||||||
|
- int age
|
||||||
|
|
||||||
|
Size: 4 bytes
|
||||||
|
|
||||||
|
- Virtual function (print)
|
||||||
|
|
||||||
|
- This makes the class polymorphic, so the compiler adds a vptr (virtual table pointer, also know as vtable pointer).
|
||||||
|
|
||||||
|
- On a 64-bit machine, a pointer is 8 bytes.
|
||||||
|
|
||||||
|
- Padding/alignment
|
||||||
|
|
||||||
|
- The compiler aligns data to certain boundaries for performance.
|
||||||
|
|
||||||
|
- Typical alignment for a class with a pointer is 8 bytes, so the 4-byte int is padded with 4 extra bytes.
|
||||||
|
|
||||||
|
### 26.6.3 Static Dispatch vs Dynamic Dispatch
|
||||||
|
|
||||||
|
- When you call a non-virtual member function like print() in the CollegeStudent class, the compiler resolves the call at compile time. This is known as static dispatch or early binding.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
CollegeStudent alice;
|
||||||
|
alice.print();
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's what happens under the hood:
|
||||||
|
|
||||||
|
- At compile time, the compiler sees that alice is of type CollegeStudent.
|
||||||
|
|
||||||
|
- It knows the exact location of CollegeStudent::print() in the compiled binary (it's in the .text segment).
|
||||||
|
|
||||||
|
- So it generates a direct call to that specific memory address. Like *call 0x123456* where *0x123456* is the address of the print() function.
|
||||||
|
|
||||||
|
- This is why the object doesn’t need to store any pointer to the function — the compiler already knows which function to call!
|
||||||
|
|
||||||
|
- If print() were marked virtual, like in the CSStudent class, then the call would become runtime-resolved using a vtable. Then:
|
||||||
|
|
||||||
|
- The object now gets a hidden pointer to a vtable (a lookup table of function pointers).
|
||||||
|
|
||||||
|
- When you call print(), the program:
|
||||||
|
|
||||||
|
- Looks up the function pointer in the vtable.
|
||||||
|
|
||||||
|
- Calls the function via that pointer.
|
||||||
|
|
||||||
|
- This is called dynamic dispatch or late binding.
|
||||||
|
|
||||||
|
Question: What if class CSStudent is defined as this, what would be the memory size of a CSStudent object?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class CSStudent {
|
||||||
|
int age;
|
||||||
|
virtual void print(){
|
||||||
|
std::cout << "I am a CS student." << std::endl;
|
||||||
|
}
|
||||||
|
virtual void print2(){
|
||||||
|
std::cout << "I am still a CS student." << std::endl;
|
||||||
|
}
|
||||||
|
virtual void print3(){
|
||||||
|
std::cout << "I am still a CS student." << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
To understand the problem, compile this [program](virtual3.cpp), and use the tool *pahole* to examine the memory information.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ g++ -g virtual3.cpp
|
||||||
|
$ pahole a.out
|
||||||
|
class CSStudent {
|
||||||
|
public:
|
||||||
|
|
||||||
|
void ~CSStudent(class CSStudent *, int);
|
||||||
|
|
||||||
|
void CSStudent(class CSStudent *, );
|
||||||
|
|
||||||
|
void CSStudent(class CSStudent *, const class CSStudent &);
|
||||||
|
|
||||||
|
void CSStudent(class CSStudent *);
|
||||||
|
|
||||||
|
int ()(void) * * _vptr.CSStudent; /* 0 8 */
|
||||||
|
int age; /* 8 4 */
|
||||||
|
virtual void print(class CSStudent *);
|
||||||
|
|
||||||
|
virtual void print2(class CSStudent *);
|
||||||
|
|
||||||
|
virtual void print3(class CSStudent *);
|
||||||
|
|
||||||
|
/* vtable has 3 entries: {
|
||||||
|
[0] = print((null)),
|
||||||
|
[1] = print2((null)),
|
||||||
|
[2] = print3((null)),
|
||||||
|
} */
|
||||||
|
/* size: 16, cachelines: 1, members: 2 */
|
||||||
|
/* padding: 4 */
|
||||||
|
/* last cacheline: 16 bytes */
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The numbers "0 8" mean that the virtual table pointer starts at offset 0 of the class, and it has 8 bytes; the numbers "8 4" means that the variable *age* starts at offset 8 and it has 4 bytes.
|
||||||
|
|
||||||
|
## 26.7 Virtual Destructor
|
||||||
|
|
||||||
|
- If the destructor of the base class is not virtual, deleting an object through a base pointer results in undefined behavior — most likely, the derived class’s destructor won’t be called, which leads to resource leaks.
|
||||||
|
|
||||||
|
### Example (Wrong: no virtual destructor)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
~Base() {
|
||||||
|
std::cout << "Base destructor\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
~Derived() {
|
||||||
|
std::cout << "Derived destructor\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Base* b = new Derived();
|
||||||
|
delete b; // ⚠️ Only Base destructor is called!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- ❌ Derived's destructor is not called — this causes resource leaks if Derived manages any resources.
|
||||||
|
|
||||||
|
### ✅ Example (Correct: virtual destructor)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
virtual ~Base() {
|
||||||
|
std::cout << "Base destructor\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
~Derived() override {
|
||||||
|
std::cout << "Derived destructor\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Base* b = new Derived();
|
||||||
|
delete b; // ✅ Both destructors are called
|
||||||
|
}
|
||||||
|
```
|
||||||
29
lectures/26_inheritance_II/diamond_test1.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Human {
|
||||||
|
public:
|
||||||
|
Human() { std::cout << "Human constructor\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Student : public Human {
|
||||||
|
public:
|
||||||
|
Student() { std::cout << "Student constructor\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Worker : public Human {
|
||||||
|
public:
|
||||||
|
Worker() { std::cout << "Worker constructor\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class CSStudent : public Student, public Worker {
|
||||||
|
public:
|
||||||
|
CSStudent() { std::cout << "CSStudent constructor\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// problem with this program: Human constructor runs twice!
|
||||||
|
int main() {
|
||||||
|
// CSStudent has two copies of Human, one via Student and one via Worker.
|
||||||
|
// Ambiguity: If we try to access a Human member from CSStudent, the compiler doesn’t know which one we mean.
|
||||||
|
CSStudent cs;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
32
lectures/26_inheritance_II/diamond_test2.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class Human {
|
||||||
|
public:
|
||||||
|
Human() { std::cout << "Human constructor\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// The virutal keyword tells the compiler:
|
||||||
|
// "Hey, if this class is used in a diamond-like hierarchy, only one shared Human base should exist, no matter how many paths lead to it."
|
||||||
|
// The compiler then: Makes sure that only one instance of Human exists inside CSStudent.
|
||||||
|
class Student : virtual public Human {
|
||||||
|
public:
|
||||||
|
Student() { std::cout << "Student constructor\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Worker : virtual public Human {
|
||||||
|
public:
|
||||||
|
Worker() { std::cout << "Worker constructor\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class CSStudent : public Student, public Worker {
|
||||||
|
public:
|
||||||
|
CSStudent() { std::cout << "CSStudent constructor\n"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// problem with this program: Human constructor runs twice!
|
||||||
|
int main() {
|
||||||
|
// CSStudent has two copies of Human, one via Student and one via Worker.
|
||||||
|
// Ambiguity: If we try to access a Human member from CSStudent, the compiler doesn’t know which one we mean.
|
||||||
|
CSStudent cs;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||