From e71c1cc844b5ee3b06d70d8cdcdb582fc66c45e8 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 | 52 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 8a4447d..dfd0fbc 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}\\)`; @@ -422,8 +422,37 @@ 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) + { + let currentNode = tree[currentNodeIndex]; + if (!checkDivisorIsProduct(tree, currentNode)) + { + currentNodeIndex = findNextInDFS(tree, 0, currentNodeIndex); + continue; + } + + let currentRightNode = tree[currentNode.rightNode]; + // Check quotient is in form ac/b, and needs to be transformed + // (Current node is /, and right child is * + // Make a (right child of *) the right child of / + currentNode.rightNode = currentRightNode.rightNode; + let a = tree[currentNode.rightNode]; + a.parent = tree.indexOf(currentNode); + + // Make / the right child of * + currentRightNode.rightNode = tree.indexOf(currentNode); + currentNode.parent = tree.indexOf(currentRightNode); + + // TODO: If / is at front of list, we need to swap the / and * around. + currentNodeIndex = findNextInDFS(tree, 0, tree.indexOf(currentNode)); + } + + // Create a dictionary of depth:node indices + let layers = {}; + let maxLayer = 0; + for (let i = 0; i < tree.length; i++) { let currentNode = tree[currentNodeIndex]; // If commutative node found, add children to list @@ -534,6 +563,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 != "operator" || node.content != '/') + return false; + if (node.rightNode == -1) + return false; + + let currentRightNode = tree[node.rightNode]; + if (currentRightNode.type != "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 ae6ada7bdd234a44e89eab9790123c28db53269d 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 | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pages/index/script.js b/pages/index/script.js index dfd0fbc..4124711 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -434,6 +434,22 @@ function normaliseTree(tree, rootNodeIndex=0) } let currentRightNode = tree[currentNode.rightNode]; + // If / is at front of list, we need to swap the / and * around. + if (currentNodeIndex == 0) + { + currentRightNode.parent = -1; + let temp = currentRightNode; + tree[currentNode.rightNode] = currentNode; + tree[0] = temp; + + // Fix children + let b = tree[currentNode.leftNode]; + b.parent = tree.indexOf(currentNode); + + let c = tree[currentRightNode.leftNode]; + c.parent = 0; + } + // Check quotient is in form ac/b, and needs to be transformed // (Current node is /, and right child is * // Make a (right child of *) the right child of / @@ -445,7 +461,6 @@ function normaliseTree(tree, rootNodeIndex=0) currentRightNode.rightNode = tree.indexOf(currentNode); currentNode.parent = tree.indexOf(currentRightNode); - // TODO: If / is at front of list, we need to swap the / and * around. currentNodeIndex = findNextInDFS(tree, 0, tree.indexOf(currentNode)); } From b965a5a1b8931a994cf942fe0ba8cb1982d290a1 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 4124711..2272433 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -427,7 +427,7 @@ function normaliseTree(tree, rootNodeIndex=0) while (currentNodeIndex != -1) { let currentNode = tree[currentNodeIndex]; - if (!checkDivisorIsProduct(tree, currentNode)) + if (!checkDividendIsProduct(tree, currentNode)) { currentNodeIndex = findNextInDFS(tree, 0, currentNodeIndex); continue; @@ -581,8 +581,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 != "operator" || node.content != '/') From c1b2bbba44e7b8a68d013b633d8644670e27e5f7 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 2272433..3bdd205 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -464,6 +464,53 @@ function normaliseTree(tree, rootNodeIndex=0) currentNodeIndex = findNextInDFS(tree, 0, tree.indexOf(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 let layers = {}; let maxLayer = 0; @@ -587,8 +634,6 @@ function checkDividendIsProduct(tree, node) // Check if node is /, and right child is * if (node.type != "operator" || node.content != '/') return false; - if (node.rightNode == -1) - return false; let currentRightNode = tree[node.rightNode]; if (currentRightNode.type != "operator" || currentRightNode.content != '*') @@ -597,6 +642,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 3612b8684f434ec97df0d20beaade43b651fca2a 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 3bdd205..f33e531 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -489,6 +489,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 434a74aae296091c9513f33fb81c3c95a51bbc97 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 f33e531..10f56ba 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -464,7 +464,7 @@ function normaliseTree(tree, rootNodeIndex=0) currentNodeIndex = findNextInDFS(tree, 0, tree.indexOf(currentNode)); } - // Convert all quotients to form 1/b * a +// Convert all quotients to form 1/b * a currentNodeIndex = 0; while (currentNodeIndex != -1) { @@ -503,6 +503,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 344bf13eac38f38a7c0ec8d8d84341d7716632c4 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 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 10f56ba..851363b 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -532,10 +532,8 @@ function normaliseTree(tree, rootNodeIndex=0) currentNodeIndex = findNextInDFS(tree, 0, tree.indexOf(currentNode)); } - // Create a dictionary of depth:node indices - let layers = {}; - let maxLayer = 0; - for (let i = 0; i < tree.length; i++) + currentNode = rootNodeIndex; + while (currentNodeIndex != -1) { let currentNode = tree[currentNodeIndex]; // If commutative node found, add children to list From 282ffebdc1d912d8cda5ccccf97a1d26a4587967 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/index/script.js b/pages/index/script.js index 851363b..4282eee 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -503,7 +503,7 @@ function normaliseTree(tree, rootNodeIndex=0) b.parent = tree.indexOf(currentNode); } - // If not, make * the right node if its parent + // If not, make * the right node of its parent else { let multiplyNodeParent = tree[currentNode.parent]; From 8cedca7710b4c7e82ea92bcc7186a929e8924e61 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 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pages/index/script.js b/pages/index/script.js index 4282eee..0b8dcfd 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -450,6 +450,17 @@ function normaliseTree(tree, rootNodeIndex=0) c.parent = 0; } + // If not, make * child of /s parent + else + { + let currentNodeParent = tree[currentNode.parent]; + currentRightNode.parent = currentNode.parent; + if (currentNodeParent.leftNode == currentNodeIndex) + currentNodeParent.leftNode = tree.indexOf(currentRightNode); + else + currentNodeParent.rightNode = tree.indexOf(currentRightNode); + } + // Check quotient is in form ac/b, and needs to be transformed // (Current node is /, and right child is * // Make a (right child of *) the right child of / From 8439147deb0357126eba70656820a52ca0b364dc 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 0b8dcfd..bee91af 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}\\)`;