adding the map animation

This commit is contained in:
Jidong Xiao
2025-04-25 20:41:09 -04:00
committed by JamesFlare1212
parent c3588e8541
commit 50bbf8c2a7
4 changed files with 761 additions and 0 deletions

View File

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

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

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

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

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

View File

@@ -233,6 +233,8 @@ Test Case 6: false
You can also find this problem on Leetcode.
- [Leetcode problem 219: Contains Duplicate II](https://leetcode.com/problems/contains-duplicate-ii/description/). Solution: [p219_contains_duplicate_ii.cpp](../../leetcode/p219_contains_duplicate_ii.cpp).
- Play this [animation](https://jidongxiao.github.io/CSCI1200-DataStructures/animations/maps/example1.html) to understand how this program works.
## 15.11 More Leetcode Exercises
- [Leetcode problem 1: Two Sum](https://leetcode.com/problems/two-sum/). Solution: [p1_twosum.cpp](../../leetcode/p1_twosum.cpp).