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', () => {
});
});
});
+