From e3f056c40a56b0bff5bcd290f486b5e37d7d5e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20=C5=A0pa=C4=8Dek?= Date: Sat, 7 Dec 2024 00:45:24 +0100 Subject: [PATCH] Added point removal --- index.js | 25 +++++++++++++++++++++++-- m-tree/mtree.js | 26 ++++++++++++++++++++++++++ m-tree/nodes.js | 17 ++++++++++------- public/index.html | 1 + public/performance.html | 30 +++++++++++++++--------------- public/script/main.js | 20 ++++++++++++++++++++ 6 files changed, 95 insertions(+), 24 deletions(-) diff --git a/index.js b/index.js index 1caefd4..78c335a 100644 --- a/index.js +++ b/index.js @@ -137,12 +137,22 @@ app.get('/kNNQuery', (req, res) => { const result = mtree.kNNQuery(parsedQueryPoint, kInt); const mtreeKNNQueryTime = performance.now() - start; - const start2 = performance.now(); + /*const start2 = performance.now(); let sequentialFunctionCalls = 0; const sequentialResult = points.sort((a, b) => { sequentialFunctionCalls++; return mtree.distanceFunction(a, parsedQueryPoint) - mtree.distanceFunction(b, parsedQueryPoint); - }).slice(0, kInt); + }).slice(0, kInt);*/ + + const start2 = performance.now(); + let sequentialFunctionCalls = 0; + const distances = points.map(point => { + sequentialFunctionCalls++; + return { point, distance: mtree.distanceFunction(point, parsedQueryPoint) }; + }); + distances.sort((a, b) => a.distance - b.distance); + const sequentialResult = distances.slice(0, kInt).map(({ point }) => point); + const sequentialSearchTime = performance.now() - start2; /*if (JSON.stringify(result.points.map(point => point.point)) !== JSON.stringify(sequentialResult)) { @@ -226,5 +236,16 @@ app.post('/loadTree', (req, res) => { res.send('MTree loaded'); }); +// Remove the given point from the MTree +app.post('/remove', (req, res) => { + const point = req.body.point; + if (!point) + return res.status(400).send('Missing point'); + if (mtree.remove(point)) + res.send('Point removed from MTree'); + else + res.status(404).send('Point not found in MTree'); +}); + // Start the server app.listen(3000, () => { console.log('MTree API is running on port 3000'); }); \ No newline at end of file diff --git a/m-tree/mtree.js b/m-tree/mtree.js index 63e03d8..b6699d7 100644 --- a/m-tree/mtree.js +++ b/m-tree/mtree.js @@ -295,6 +295,32 @@ class MTree { } } } + + /** + * Removes a point from the MTree. + * @param {number[]} point - The point to remove + * @param {Node} [currentNode] - The node to start searching from, defaults to the root node + * @returns {boolean} True if the point was removed, false otherwise + */ + remove(point, currentNode = this.root) { + for (let i = 0; i < currentNode.entries.length; i++) { + const entry = currentNode.entries[i]; + const distance = this.distanceFunction(point, entry.point); + + if (currentNode.isLeaf) { + if (distance === 0) { + currentNode.entries.splice(i, 1); + return true; + } + } else { + if (distance <= entry.radius) { + const deleted = this.remove(point, entry); + if (deleted) return true; + } + } + } + return false; + } } module.exports = MTree; diff --git a/m-tree/nodes.js b/m-tree/nodes.js index 2facc1a..075ca6e 100644 --- a/m-tree/nodes.js +++ b/m-tree/nodes.js @@ -83,15 +83,18 @@ class Node { * @returns {GroundEntry[]} An array of ground entries found within the node. */ static findGroundEntries(node) { - if (node.isLeaf) { - return node.entries.filter(entry => entry instanceof GroundEntry); - } else { - const result = []; - for (const entry of node.entries) { - result.push(...Node.findGroundEntries(entry)); + const stack = [node]; + const result = []; + + while (stack.length > 0) { + const node = stack.pop(); + if (node.isLeaf) { + result.push(...node.entries.filter(entry => entry instanceof GroundEntry)); + } else { + stack.push(...node.entries); } - return result; } + return result; }; findGroundEntries() { diff --git a/public/index.html b/public/index.html index cd6ff74..aa7a03c 100644 --- a/public/index.html +++ b/public/index.html @@ -24,6 +24,7 @@ +
diff --git a/public/performance.html b/public/performance.html index 03ebb34..1da7b51 100644 --- a/public/performance.html +++ b/public/performance.html @@ -176,7 +176,7 @@ sequentialSearch: 0 } }; - for (let j = 0; j < 5; j++) { + for (let j = 0; j < 2; j++) { const response = await fetch('/recreate', { method: 'POST', @@ -186,7 +186,8 @@ pointCount: datasetSize, capacity: mTreeNodeSize, distanceFunction: 'euclidean' - }) + }), + timeout: 6000000 }); if (!response.ok) { throw new Error(`error recreating tree: ${await response.text()}`); @@ -195,9 +196,9 @@ const { treeCreationTime } = await response.json(); timingResults.treeCreationTime += treeCreationTime; - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 4; i++) { - const queryPoint = Array(dimension).fill(i / 5); + const queryPoint = Array(dimension).fill(i / 4); const knnResponse = await fetch(`/kNNQuery?queryPoint=${encodeURIComponent(JSON.stringify(queryPoint))}&k=${encodeURIComponent((i + 1)*3)}`); if (!knnResponse.ok) { @@ -229,20 +230,19 @@ } } - timingResults.treeCreationTime /= 5; - timingResults.rangeQueryTime.mtreeRangeQuery /= 25; - functionCallCounts.rangeQuery.mtreeRangeQuery /= 25; - timingResults.rangeQueryTime.sequentialSearch /= 25; - functionCallCounts.rangeQuery.sequentialSearch /= 25; - timingResults.knnQueryTime.mtreeKNNQuery /= 25; - functionCallCounts.knnQuery.mtreeKNNQuery /= 25; - timingResults.knnQueryTime.sequentialSearch /= 25; - functionCallCounts.knnQuery.sequentialSearch /= 25; - - return { timingResults, functionCallCounts }; + timingResults.treeCreationTime /= 2; + timingResults.rangeQueryTime.mtreeRangeQuery /= 8; + functionCallCounts.rangeQuery.mtreeRangeQuery /= 8; + timingResults.rangeQueryTime.sequentialSearch /= 8; + functionCallCounts.rangeQuery.sequentialSearch /= 8; + timingResults.knnQueryTime.mtreeKNNQuery /= 8; + functionCallCounts.knnQuery.mtreeKNNQuery /= 8; + timingResults.knnQueryTime.sequentialSearch /= 8; + functionCallCounts.knnQuery.sequentialSearch /= 8; const endTime = performance.now(); console.log(`finished at ${endTime} after ${endTime - startTime}ms`); + return { timingResults, functionCallCounts }; } catch (error) { console.error('error running tests:', error); return {}; diff --git a/public/script/main.js b/public/script/main.js index b57a7b9..d89130b 100644 --- a/public/script/main.js +++ b/public/script/main.js @@ -20,6 +20,7 @@ function toggleButtons(enable) { document.getElementById('rangeQueryButton').disabled = !enable; document.getElementById('knnQueryButton').disabled = !enable; document.getElementById('insertButton').disabled = !enable; + document.getElementById('removeButton').disabled = !enable; document.getElementById('saveTreeButton').disabled = !enable; } @@ -208,6 +209,23 @@ async function loadTree() { reader.readAsText(file); } +/** + * Removes the point with the given coordinates from the MTree. + * After removing the point, updates the status element with a success or failure message. + */ +async function removePoint() { + const pointString = document.getElementById('point').value; + const point = JSON.parse(pointString); + + const response = await fetch('/remove', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ point }) + }); + setStatus(response.ok); + document.getElementById('status').innerText = response.ok ? 'Point removed' : `Error: ${await response.text()}`; +} + /** * Sets the status element to display a success or failure message. * @param {boolean} success - Whether the operation was successful. @@ -223,6 +241,7 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('rangeQueryButton').addEventListener('click', performRangeQuery); document.getElementById('knnQueryButton').addEventListener('click', performKNNQuery); document.getElementById('insertButton').addEventListener('click', insertPoint); + document.getElementById('removeButton').addEventListener('click', removePoint); document.getElementById('recreateTreeButton').addEventListener('click', recreateTree); document.getElementById('saveTreeButton').addEventListener('click', saveTree); @@ -243,3 +262,4 @@ document.addEventListener('DOMContentLoaded', () => { }); }); }); +