-
Notifications
You must be signed in to change notification settings - Fork 0
<fix> Normalise quotients to form a/b * c #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
e62391d to
bf8d4db
Compare
bf8d4db to
f7fd0e6
Compare
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!
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.
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.
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!
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.
f7fd0e6 to
434a74a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements normalization of division expressions to ensure consistent expression tree structures. The change adds two normalization passes that convert quotients into a standardized form: ac/b → 1/b * a * c and a/b → 1/b * a, enabling more reliable comparison of mathematically equivalent expressions.
Key changes:
- Added two normalization passes to the
normaliseTreefunction to standardize division operations - Implemented helper functions
checkDividendIsProductandcheckDividendIsNot1to identify expressions needing normalization - Temporarily disabled
normaliseTreecall at initialization (marked with TODO)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let multiplyNode = { | ||
| content: '*', | ||
| type: "operator", | ||
| leftNode: tree.indexOf(a), | ||
| rightNode: currentNodeIndex, | ||
| parent: currentNode.parent, | ||
| depth: -1 | ||
| }; | ||
|
|
||
| tree.push(multiplyNode); |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When creating the multiplyNode, leftNode is set to tree.indexOf(a) which is the index before the multiplyNode is pushed to the tree. However, after tree.push(multiplyNode), if currentNodeIndex is 0, the code swaps positions of nodes in the tree array. This swap invalidates the leftNode index that was stored. The leftNode should be recalculated or set after all tree modifications are complete.
pages/index/script.js
Outdated
| // Create a dictionary of depth:node indices | ||
| let layers = {}; | ||
| let maxLayer = 0; | ||
| for (let i = 0; i < tree.length; i++) |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop variable i is used in the for loop declaration, but inside the loop body, currentNodeIndex is being used to access tree elements. This appears to be a copy-paste error from the old code. The variable currentNodeIndex should be set to i at the start of the loop body, or the loop should be changed to iterate using currentNodeIndex instead of i.
| for (let i = 0; i < tree.length; i++) | |
| currentNodeIndex = rootNodeIndex; | |
| while (currentNodeIndex != -1) |
| // 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 |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment has a typo: "true is dividend" should be "true if dividend".
| // RETURNS: bool true is dividend is not 1, false if it is | |
| // RETURNS: bool true if dividend is not 1, false if it is |
| 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); | ||
| } |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When currentNodeIndex is 0, the code swaps multiplyNode and currentNode positions in the tree array. However, the multiplyNode.leftNode value was set to tree.indexOf(a) before the swap. After the swap at lines 496-497, this index may no longer point to the correct node if 'a' was also at index 0 or at the position where multiplyNode ends up. The leftNode reference should be updated after the swap to ensure it points to the correct node.
| // Change current node index to index of * (to traverse over a) | ||
| currentNodeIndex = tree.indexOf(multiplyNode); | ||
|
|
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable currentNodeIndex is set to tree.indexOf(multiplyNode) at line 530, but then immediately overwritten at line 532 with findNextInDFS result. The assignment at line 530 appears to be dead code and serves no purpose. Either remove this line or clarify its intent in a comment.
| // Change current node index to index of * (to traverse over a) | |
| currentNodeIndex = tree.indexOf(multiplyNode); | |
| // Move to the next node in DFS after the updated current node |
| a.parent = tree.indexOf(currentNode); | ||
|
|
||
| // Make / the right child of * | ||
| currentRightNode.rightNode = tree.indexOf(currentNode); | ||
| currentNode.parent = tree.indexOf(currentRightNode); |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After tree manipulation that modifies indices by pushing new nodes and swapping positions, using tree.indexOf() to search for nodes can be inefficient and error-prone. The code relies on object reference equality, but if the tree is modified extensively, this could lead to unexpected behavior. Consider tracking indices directly or using a more robust identification system for nodes.
| // 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 dividend is product, false if not | ||
| function checkDividendIsProduct(tree, node) |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name uses "dividend" but it should be "divisor". In the expression a/b, "a" is the dividend (numerator) and "b" is the divisor (denominator). This function checks if the right child (which represents "a" in the tree structure) is a product, but the comment and function name are misleading. The function should be named checkDivisorIsProduct or the comment should clarify the tree structure.
| // 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 |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly to checkDividendIsProduct, this function name uses "dividend" but should use "divisor" for mathematical accuracy. In a/b, the function checks if "a" (the right node) is not "1", but "a" would be the divisor in the tree representation, not the dividend. The function should be named checkDivisorIsNot1 for clarity.
| // 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 | |
| // Checks whether current node is a /, and its divisor 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 if divisor is not 1, false if it is | |
| function checkDivisorIsNot1(tree, node) | |
| { | |
| // Check if node is /, and right child (the divisor) is not 1 |
pages/index/script.js
Outdated
| let layers = {}; | ||
| let maxLayer = 0; |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable layers.
| let layers = {}; | |
| let maxLayer = 0; |
pages/index/script.js
Outdated
|
|
||
| // Create a dictionary of depth:node indices | ||
| let layers = {}; | ||
| let maxLayer = 0; |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable maxLayer.
| let maxLayer = 0; |
Think it got there due to a poor rebase. Legacy code meant quotient normalisation wouldn't work at all.
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.
Solution needs to be stored in normalised form. This was removed during testing, and has now been re-added

Quotients of form ac/b and a/b * c have differing expression trees (see attached image).

So do a/b and 1/b * a.
Essentially, this change adds a pass that converts all expressions of the form ac/b -> 1/b * a * c