From cb831d4e4bc688eee3e46b453ca51f289b32a202 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Thu, 1 Jan 2026 00:35:28 +0000 Subject: [PATCH 01/10] Normalise quotients to form a/b * c Quotients of form ac/b and a/b * c have differing expression trees (see attached image). Essentially, this change adds a pass that converts ac/b -> a/b * c. FIXME: Current question can't be normalised?? May be to do with the 2nd TODO. TODO: Because this change essentially swaps the / and * nodes around, the * and / need to physically swap places in the list if the / is the root node. TODO: This may still not make 1/b * c == c/b. This needs to be looked at! --- pages/index/script.js | 54 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index cef665c..ba40208 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -1,6 +1,6 @@ // TODO: Eventually, put this in a separate file let RAW_SOLUTION = "1/2 x^2 sin(2x) + 1/2 x cos(2x) - 1/4 sin(2x) + c"; -let SOLUTION = normaliseTree(strToTree(RAW_SOLUTION)); +let SOLUTION = strToTree(RAW_SOLUTION); // TODO: Add normaliseTree back let answerText = document.getElementById("answerText"); // Answer that appears on win modal answerText.innerText = `\\(${RAW_SOLUTION}\\)`; @@ -382,9 +382,40 @@ function postfixToTree(components, index=0, parentIndex=-1, depth=0) // RETURNS: list normalised tree function normaliseTree(tree, rootNodeIndex=0) { - let currentNodeIndex = rootNodeIndex; + // Convert all quotients to form a/b * c + let currentNodeIndex = 0; while (currentNodeIndex != -1) { + // Check quotient is in form ac/b, and needs to be transformed + // (Current node is /, and right child is *) + let currentNode = tree.Get(currentNodeIndex); + if (!checkDivisorIsProduct(tree, currentNode)) + { + currentNodeIndex = findNextInDFS(tree, 0, currentNodeIndex); + continue; + } + + // Make a (right child of *) the right child of / + let currentRightNode = tree.Get(currentNode.rightNode); + currentNode.rightNode = currentRightNode.rightNode; + let a = tree.Get(currentNode.rightNode); + a.parent = tree.Find(currentNode); + + // Make / the right child of * + currentRightNode.rightNode = tree.Find(currentNode); + currentNode.parent = tree.Find(currentRightNode); + + // TODO: If / is at front of list, we need to swap the / and * around. + currentNodeIndex = findNextInDFS(tree, 0, tree.Find(currentNode)); + } + + // Create a dictionary of depth:node indices + currentNodeIndex = tree.root; + console.log(tree); + for (let i = 0; i < tree.length; i++) + { + console.log(i); + console.log(currentNodeIndex); let currentNode = tree.Get(currentNodeIndex); // If commutative node found, add children to list if (currentNode.type == NodeType.OPERATOR && currentNode.commutative == true) @@ -494,6 +525,25 @@ function findCommutativeNodes(tree, opNodeIndex, operator) "parents": commutativeParentsList}; } +// Checks whether current node is a /, and if its dividend is a product. +// Essentially, check for divisions in form of ab/c (as these need to be normalised to a/b * c) +// INPUTS: tree, node in tree +// RETURNS: bool true if divisor is product, false if not +function checkDivisorIsProduct(tree, node) +{ + // Check if node is /, and right child is * + if (node.type != NodeType.OPERATOR || node.content != '/') + return false; + if (node.rightNode == -1) + return false; + + let currentRightNode = tree.Get(node.rightNode); + if (currentRightNode.type != NodeType.OPERATOR || currentRightNode.content != '*') + return false; + + return true; +} + // Compares 2 binary trees, and returns true if their contents are equal. // INPUTS: 2 binary trees - b1, b2 // RETURNS: bool - are they equal? From 5605f1df03eaacce38b0936295b8b4d66d283e99 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Thu, 1 Jan 2026 00:57:39 +0000 Subject: [PATCH 02/10] Fix case where / is root node If / is root node of expression, the / and * need to swap. This is because the transformation ends up with the * being the logical root of the tree, and our tree traversal implementations rely on the root of the tree being at the front of the list. --- pages/index/script.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index ba40208..1e8db55 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -397,6 +397,9 @@ function normaliseTree(tree, rootNodeIndex=0) // Make a (right child of *) the right child of / let currentRightNode = tree.Get(currentNode.rightNode); + + // If / is root node, make * root node + tree.root = tree.Find(currentRightNode); currentNode.rightNode = currentRightNode.rightNode; let a = tree.Get(currentNode.rightNode); a.parent = tree.Find(currentNode); @@ -411,11 +414,8 @@ function normaliseTree(tree, rootNodeIndex=0) // Create a dictionary of depth:node indices currentNodeIndex = tree.root; - console.log(tree); for (let i = 0; i < tree.length; i++) { - console.log(i); - console.log(currentNodeIndex); let currentNode = tree.Get(currentNodeIndex); // If commutative node found, add children to list if (currentNode.type == NodeType.OPERATOR && currentNode.commutative == true) From ad729179aaf2153701ba37b98f07fbf7e47cf9f8 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Thu, 1 Jan 2026 13:27:59 +0000 Subject: [PATCH 03/10] Correct name of checkDividendIsProduct Function checkDivisorIsProduct is renamed to checkDividendIsProduct, as that is what the function does. In the quotient a/b, a is the dividend, and b is the divisor. --- pages/index/script.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 1e8db55..06f6757 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -389,7 +389,7 @@ function normaliseTree(tree, rootNodeIndex=0) // Check quotient is in form ac/b, and needs to be transformed // (Current node is /, and right child is *) let currentNode = tree.Get(currentNodeIndex); - if (!checkDivisorIsProduct(tree, currentNode)) + if (!checkDividendIsProduct(tree, currentNode)) { currentNodeIndex = findNextInDFS(tree, 0, currentNodeIndex); continue; @@ -528,8 +528,8 @@ function findCommutativeNodes(tree, opNodeIndex, operator) // Checks whether current node is a /, and if its dividend is a product. // Essentially, check for divisions in form of ab/c (as these need to be normalised to a/b * c) // INPUTS: tree, node in tree -// RETURNS: bool true if divisor is product, false if not -function checkDivisorIsProduct(tree, node) +// RETURNS: bool true if dividend is product, false if not +function checkDividendIsProduct(tree, node) { // Check if node is /, and right child is * if (node.type != NodeType.OPERATOR || node.content != '/') From 95fab4fc70ec0a13b82f9cd4a6006740a003975c Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Thu, 1 Jan 2026 14:15:24 +0000 Subject: [PATCH 04/10] Remove redundant check in checkDividendIsProduct A / node in a valid tree always has a right child, and we can assume that the input to this function is a valid tree. May need to add tree validation at some step though! --- pages/index/script.js | 66 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 06f6757..1054b45 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -412,6 +412,53 @@ function normaliseTree(tree, rootNodeIndex=0) currentNodeIndex = findNextInDFS(tree, 0, tree.Find(currentNode)); } + // Convert all quotients to form 1/b * a + currentNodeIndex = 0; + while (currentNodeIndex != -1) + { + let currentNode = tree[currentNodeIndex]; + if (!checkDividendIsNot1(tree, currentNode)) + { + currentNodeIndex = findNextInDFS(tree, 0, currentNodeIndex); + continue; + } + + let a = tree[currentNode.rightNode]; + + // Add * node + let multiplyNode = { + content: '*', + type: "operator", + leftNode: tree.indexOf(a), + rightNode: currentNodeIndex, + parent: currentNode.parent, + depth: -1 + }; + + tree.push(multiplyNode); + + currentNode.parent = tree.indexOf(multiplyNode); + a.parent = tree.indexOf(multiplyNode); + + // Add 1 node + let oneNode = { + content: '1', + type: "number", + leftNode: -1, + rightNode: -1, + parent: tree.indexOf(currentNode), + depth: -1 + }; + + tree.push(oneNode); + currentNode.rightNode = tree.indexOf(oneNode); + + // Change current node index to index of * (to traverse over a) + currentNodeIndex = tree.indexOf(multiplyNode); + + currentNodeIndex = findNextInDFS(tree, 0, tree.indexOf(currentNode)); + } + // Create a dictionary of depth:node indices currentNodeIndex = tree.root; for (let i = 0; i < tree.length; i++) @@ -534,8 +581,6 @@ function checkDividendIsProduct(tree, node) // Check if node is /, and right child is * if (node.type != NodeType.OPERATOR || node.content != '/') return false; - if (node.rightNode == -1) - return false; let currentRightNode = tree.Get(node.rightNode); if (currentRightNode.type != NodeType.OPERATOR || currentRightNode.content != '*') @@ -544,6 +589,23 @@ function checkDividendIsProduct(tree, node) return true; } +// Checks whether current node is a /, and is dividend is something other than 1 +// Essentially, checks for divisions in the form of a/b (as these need to be normalised to 1/b * a) +// INPUTS: tree, node in tree +// RETURNS: bool true is dividend is not 1, false if it is +function checkDividendIsNot1(tree, node) +{ + // Check if node is /, and right child is not 1 + if (node.type != "operator" || node.content != '/') + return false; + + let currentRightNode = tree[node.rightNode]; + if (currentRightNode.type == "number" && currentRightNode.content == '1') + return false; + + return true; +} + // Compares 2 binary trees, and returns true if their contents are equal. // INPUTS: 2 binary trees - b1, b2 // RETURNS: bool - are they equal? From de363be7ba4baa6f73c1330c2c6617b60d837df3 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Thu, 1 Jan 2026 17:24:24 +0000 Subject: [PATCH 05/10] Fix case in a/b -> 1/b * a where / is root node See e5c7de081d7b778fdfcbf79c8faa5dc392e13568 --- pages/index/script.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pages/index/script.js b/pages/index/script.js index 1054b45..5c326af 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -437,6 +437,20 @@ function normaliseTree(tree, rootNodeIndex=0) tree.push(multiplyNode); + // If / is at front of list, we need to swap the / and * around. + if (currentNodeIndex == 0) + { + let temp = multiplyNode; + tree[tree.indexOf(multiplyNode)] = currentNode; + tree[0] = temp; + + multiplyNode.parent = -1; + multiplyNode.rightNode = tree.indexOf(currentNode); + // Fix children of / + let b = tree[currentNode.leftNode]; + b.parent = tree.indexOf(currentNode); + } + currentNode.parent = tree.indexOf(multiplyNode); a.parent = tree.indexOf(multiplyNode); From 2ddd1e778c772d09a02e438aa321c7203b408504 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sun, 4 Jan 2026 19:02:47 +0000 Subject: [PATCH 06/10] Correctly add new * node to graph in a/b -> 1/b * a If / node wasn't the root node, then this pass would fail, because the created * node was not set as its parent's right node. --- pages/index/script.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pages/index/script.js b/pages/index/script.js index 5c326af..d28223c 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -412,7 +412,7 @@ function normaliseTree(tree, rootNodeIndex=0) currentNodeIndex = findNextInDFS(tree, 0, tree.Find(currentNode)); } - // Convert all quotients to form 1/b * a +// Convert all quotients to form 1/b * a currentNodeIndex = 0; while (currentNodeIndex != -1) { @@ -451,6 +451,13 @@ function normaliseTree(tree, rootNodeIndex=0) b.parent = tree.indexOf(currentNode); } + // If not, make * the right node if its parent + else + { + let multiplyNodeParent = tree[currentNode.parent]; + multiplyNodeParent.rightNode = tree.indexOf(multiplyNode); + } + currentNode.parent = tree.indexOf(multiplyNode); a.parent = tree.indexOf(multiplyNode); From 88ed1f224906248d8f547d5be436f66bdc3eaae6 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Mon, 5 Jan 2026 20:52:49 +0000 Subject: [PATCH 07/10] Remove legacy code Think it got there due to a poor rebase. Legacy code meant quotient normalisation wouldn't work at all. --- pages/index/script.js | 65 +++++++++++++------------------------------ 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index d28223c..8fc8ba8 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -416,73 +416,48 @@ function normaliseTree(tree, rootNodeIndex=0) currentNodeIndex = 0; while (currentNodeIndex != -1) { - let currentNode = tree[currentNodeIndex]; + let currentNode = tree.Get(currentNodeIndex); if (!checkDividendIsNot1(tree, currentNode)) { currentNodeIndex = findNextInDFS(tree, 0, currentNodeIndex); continue; } - let a = tree[currentNode.rightNode]; + let a = tree.Get(currentNode.rightNode); // Add * node - let multiplyNode = { - content: '*', - type: "operator", - leftNode: tree.indexOf(a), - rightNode: currentNodeIndex, - parent: currentNode.parent, - depth: -1 - }; - - tree.push(multiplyNode); + let multiplyNode = new Node("operator", Operator.MULTIPLICATION, leftNode=tree.Find(a), rightNode=currentNodeIndex); // If / is at front of list, we need to swap the / and * around. - if (currentNodeIndex == 0) + if (tree.Find(currentNode) == tree.root) { - let temp = multiplyNode; - tree[tree.indexOf(multiplyNode)] = currentNode; - tree[0] = temp; - - multiplyNode.parent = -1; - multiplyNode.rightNode = tree.indexOf(currentNode); - // Fix children of / - let b = tree[currentNode.leftNode]; - b.parent = tree.indexOf(currentNode); + tree.AddAsRoot(multiplyNode); } - // If not, make * the right node if its parent else { - let multiplyNodeParent = tree[currentNode.parent]; - multiplyNodeParent.rightNode = tree.indexOf(multiplyNode); + let multiplyNodeParent = tree.Get(currentNode.parent); + multiplyNodeParent.rightNode = -1; + tree.Add(multiplyNode, multiplyNodeParent); } - currentNode.parent = tree.indexOf(multiplyNode); - a.parent = tree.indexOf(multiplyNode); + currentNode.parent = tree.Find(multiplyNode); + a.parent = tree.Find(multiplyNode); // Add 1 node - let oneNode = { - content: '1', - type: "number", - leftNode: -1, - rightNode: -1, - parent: tree.indexOf(currentNode), - depth: -1 - }; - - tree.push(oneNode); - currentNode.rightNode = tree.indexOf(oneNode); + let oneNode = newNode("number", '1'); + tree.Add(oneNode, currentNode); + + currentNode.rightNode = tree.Find(oneNode); // Change current node index to index of * (to traverse over a) - currentNodeIndex = tree.indexOf(multiplyNode); + currentNodeIndex = tree.Find(multiplyNode); - currentNodeIndex = findNextInDFS(tree, 0, tree.indexOf(currentNode)); + currentNodeIndex = findNextInDFS(tree, 0, tree.Find(currentNode)); } - // Create a dictionary of depth:node indices currentNodeIndex = tree.root; - for (let i = 0; i < tree.length; i++) + while (currentNodeIndex != -1) { let currentNode = tree.Get(currentNodeIndex); // If commutative node found, add children to list @@ -617,11 +592,11 @@ function checkDividendIsProduct(tree, node) function checkDividendIsNot1(tree, node) { // Check if node is /, and right child is not 1 - if (node.type != "operator" || node.content != '/') + if (node.type != NodeType.OPERATOR || node.content != '/') return false; - let currentRightNode = tree[node.rightNode]; - if (currentRightNode.type == "number" && currentRightNode.content == '1') + let currentRightNode = tree.Get(node.rightNode); + if (currentRightNode.type == NodeType.NUMBER && currentRightNode.content == '1') return false; return true; From 324a061daa157e2a200b31b4dfb9bf6512894274 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Mon, 5 Jan 2026 20:54:10 +0000 Subject: [PATCH 08/10] Fix spelling mistake in comment --- pages/index/script.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/index/script.js b/pages/index/script.js index 8fc8ba8..85607fd 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -433,7 +433,8 @@ function normaliseTree(tree, rootNodeIndex=0) { tree.AddAsRoot(multiplyNode); } - // If not, make * the right node if its parent + + // If not, make * the right node of its parent else { let multiplyNodeParent = tree.Get(currentNode.parent); From 1d1250c5248a903515ec7aace511d2cb88fe8069 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Mon, 5 Jan 2026 21:03:38 +0000 Subject: [PATCH 09/10] Make * node child of /s parent in ac/b -> a/b * c In this transformation, we essentially swap the * and / nodes around. However, when / wasn't the root node, * wasn't getting correctly set as /'s parent's child. This has now been fixed. --- pages/index/script.js | 39 +++++++++++++++++++++++++++++---------- pages/index/tree.js | 9 +++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 85607fd..f907edd 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -395,21 +395,40 @@ function normaliseTree(tree, rootNodeIndex=0) continue; } + let divisionNode = currentNode; // Make a (right child of *) the right child of / - let currentRightNode = tree.Get(currentNode.rightNode); + let multiplicationNode = tree.Get(divisionNode.rightNode); - // If / is root node, make * root node - tree.root = tree.Find(currentRightNode); - currentNode.rightNode = currentRightNode.rightNode; - let a = tree.Get(currentNode.rightNode); - a.parent = tree.Find(currentNode); + // If / is root node, we need to make * root node + if (tree.Find(currentNode) == tree.root) + { + tree.root = tree.Find(multiplyNode); + } + + // If not, make * the child of /'s parent + else + { + let divisionNodeParent = tree.Get(divisionNode.parent); + if (divisionNodeParent.leftNode == tree.Find(divisionNode)) + { + divisionNodeParent.leftNode = tree.Find(multiplicationNode); + } + else if (divisionNodeParent.rightNode == tree.Find(divisionNode)) + { + divisionNodeParent.rightNode = tree.Find(multiplicationNode); + } + } + + divisionNode.rightNode = multiplicationNode.rightNode; + let a = tree.Get(divisionNode.rightNode); + a.parent = tree.Find(divisionNode); // Make / the right child of * - currentRightNode.rightNode = tree.Find(currentNode); - currentNode.parent = tree.Find(currentRightNode); + multiplicationNode.rightNode = tree.Find(divisionNode); + divisionNode.parent = tree.Find(multiplicationNode); // TODO: If / is at front of list, we need to swap the / and * around. - currentNodeIndex = findNextInDFS(tree, 0, tree.Find(currentNode)); + currentNodeIndex = findNextInDFS(tree, 0, tree.Find(divisionNode)); } // Convert all quotients to form 1/b * a @@ -428,7 +447,7 @@ function normaliseTree(tree, rootNodeIndex=0) // Add * node let multiplyNode = new Node("operator", Operator.MULTIPLICATION, leftNode=tree.Find(a), rightNode=currentNodeIndex); - // If / is at front of list, we need to swap the / and * around. + // If / is root node, we need to make * root node if (tree.Find(currentNode) == tree.root) { tree.AddAsRoot(multiplyNode); diff --git a/pages/index/tree.js b/pages/index/tree.js index c24adef..7843f66 100644 --- a/pages/index/tree.js +++ b/pages/index/tree.js @@ -255,6 +255,15 @@ class Tree { this.#_body.push(node); return; } + + // Add a node to the tree as the root node. Note: The previous root node will need its parent node to be assigned separately. + // INPUTS: Node to add as root + // RETURNS: none. + AddAsRoot(node) { + this.#_body.push(node); + this.root = this.Find(node); + return; + } // Remove a specified node from the graph. // INPUTS: Node to remove From f8381833f5559373bb9c6d236c9c71c9ccce4dd6 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sun, 11 Jan 2026 16:41:58 +0000 Subject: [PATCH 10/10] Add normaliseTree back to solution Solution needs to be stored in normalised form. This was removed during testing, and has now been re-added --- pages/index/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/index/script.js b/pages/index/script.js index f907edd..b08caf1 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -1,6 +1,6 @@ // TODO: Eventually, put this in a separate file let RAW_SOLUTION = "1/2 x^2 sin(2x) + 1/2 x cos(2x) - 1/4 sin(2x) + c"; -let SOLUTION = strToTree(RAW_SOLUTION); // TODO: Add normaliseTree back +let SOLUTION = normaliseTree(strToTree(RAW_SOLUTION)); // TODO: Add normaliseTree back let answerText = document.getElementById("answerText"); // Answer that appears on win modal answerText.innerText = `\\(${RAW_SOLUTION}\\)`;