Skip to content

Commit 3904acd

Browse files
committed
<fix> Allow multi commutativity
Expressions where multiple commutative operators are placed next to each other (e.g: 1+2+3) were struggling to normalise to the same thing. This is because commutativity was only expected to exist on one operator at a time - basically, the normaliser was dealing with 1+2+3 as (1+2)+3. By going top-down and looking for "chains" of the same commutative operator, this can be fixed.
1 parent ddec3fd commit 3904acd

1 file changed

Lines changed: 103 additions & 26 deletions

File tree

pages/index/script.js

Lines changed: 103 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -420,41 +420,118 @@ function postfixToTree(components, index=0, parentIndex=-1, depth=0)
420420
// As there are multiple ways to write the same maths expression, there are multiple graphs that map to equivalent expressions. To compare equality of 2 graphs, they must first both be normalised (essentially, sorting elements under commutative operators by their content)
421421
// INPUTS: list tree
422422
// RETURNS: list normalised tree
423-
function normaliseTree(tree)
423+
function normaliseTree(tree, rootNodeIndex=0)
424424
{
425-
// Create a dictionary of depth:node indices
426-
let layers = {};
427-
let maxLayer = 0;
428-
for (let i = 0; i < tree.length; i++)
425+
let currentNodeIndex = rootNodeIndex;
426+
while (currentNodeIndex != -1)
429427
{
430-
// If layer isn't in dict yet, add it
431-
if (layers[tree[i].depth.toString()] == null)
432-
layers[tree[i].depth.toString()] = [];
433-
// Add node to layer
434-
layers[tree[i].depth.toString()].push(i);
435-
maxLayer = (tree[i].depth > maxLayer) ? tree[i].depth : maxLayer;
428+
let currentNode = tree[currentNodeIndex];
429+
// If commutative node found, add children to list
430+
if (currentNode.type == "operator" && currentNode.commutative == true)
431+
{
432+
let terms = findCommutativeNodes(tree, currentNodeIndex, currentNode.content);
433+
let commutativeNodes = terms.nodes;
434+
let locationsToPutCommutativeNodes = terms.parents;
435+
436+
// Sort nodes in list by content
437+
commutativeNodes = sortCommutativeNodes(tree, commutativeNodes);
438+
// Add nodes back to tree in content order
439+
for (let i = 0; i < locationsToPutCommutativeNodes.length; i++)
440+
{
441+
// Link parent to child
442+
let location = locationsToPutCommutativeNodes[i];
443+
if (location.side == 'L')
444+
tree[location.index].leftNode = commutativeNodes[i];
445+
else
446+
tree[location.index].rightNode = commutativeNodes[i];
447+
448+
// Link child to parent
449+
tree[commutativeNodes[i]].parent = location.index;
450+
}
451+
}
452+
// DFS through tree
453+
currentNodeIndex = findNextInDFS(tree, rootNodeIndex, currentNodeIndex);
436454
}
437-
// For each layer,
438-
for (let i = maxLayer; i > 0; i--)
455+
return tree;
456+
}
457+
458+
// Sorts nodes that are commutative under an operator by their content
459+
// This is so they can be moved around the tree to normalise it
460+
// INPUTS: tree, list of nodes
461+
// RETURNS: list[int] of node indices, ordered by content
462+
function sortCommutativeNodes(tree, nodes)
463+
{
464+
// Implementing a bubble sort here, as lists should be very small (min 2, rarely much greater)
465+
for (let i = 1; i < nodes.length; i++)
439466
{
440-
// Go up one layer - find commutative nodes with 2 kids
441-
for (let parentIndex of layers[i-1])
467+
for (let j = 0; j < nodes.length - i; j++)
442468
{
443-
let parentNode = tree[parentIndex];
444-
if (parentNode.type == "operator" && parentNode.commutative == true)
469+
if (evaluateIfSwapNeeded(tree, nodes[j], nodes[j+1]))
445470
{
446-
// If so, compare contents. If R < L, swap parent's child pointers around
447-
let requiresSwap = evaluateIfSwapNeeded(tree, parentNode.leftNode, parentNode.rightNode);
448-
if (requiresSwap)
449-
{
450-
let temp = parentNode.rightNode;
451-
parentNode.rightNode = parentNode.leftNode;
452-
parentNode.leftNode = temp;
453-
}
471+
let temp = nodes[j];
472+
nodes[j] = nodes[j+1];
473+
nodes[j+1] = temp;
454474
}
455475
}
456476
}
457-
return tree;
477+
return nodes;
478+
}
479+
480+
// Finds nodes in expression tree that are commutative under an operator in the expression, and their location relative to their parent
481+
// INPUTS: tree, int index of operator node, str type of operator (* or +)
482+
// RETURNS: list[int] of indices that are commutative under the operator tree[n], where n is the first currentNode,
483+
// list[obj] of locations of nodes, relative to their parent ({index of parent, side of parent node is on}).
484+
function findCommutativeNodes(tree, opNodeIndex, operator)
485+
{
486+
let opNode = tree[opNodeIndex];
487+
let commutativeNodesList = [];
488+
let commutativeParentsList = [];
489+
let nodesToCheck = [];
490+
491+
let leftNodeIndex = opNode.leftNode;
492+
let leftNode = tree[leftNodeIndex];
493+
nodesToCheck.push(leftNode);
494+
495+
let rightNodeIndex = opNode.rightNode;
496+
let rightNode = tree[rightNodeIndex];
497+
nodesToCheck.push(rightNode);
498+
499+
for (const node of nodesToCheck)
500+
{
501+
if (node.type != "operator" || node.commutative == false)
502+
{
503+
commutativeNodesList.push(tree.indexOf(node));
504+
let parentNode = tree[node.parent];
505+
commutativeParentsList.push({
506+
"index": node.parent,
507+
"side": parentNode.leftNode == tree.indexOf(node) ? 'L' : 'R'
508+
});
509+
continue;
510+
}
511+
512+
// If commutative node of different type (* instead of +) found, normalise that subtree, then add to list
513+
if (node.content != operator)
514+
{
515+
normaliseTree(tree, tree.indexOf(node));
516+
commutativeNodesList.push(tree.indexOf(node));
517+
let parentNode = tree[node.parent];
518+
commutativeParentsList.push({
519+
"index": node.parent,
520+
"side": parentNode.leftNode == tree.indexOf(node) ? 'L' : 'R'
521+
});
522+
continue;
523+
}
524+
// If commutative node of same type found, check children
525+
let leftNodeIndex = node.leftNode;
526+
let leftNode = tree[leftNodeIndex];
527+
nodesToCheck.push(leftNode);
528+
529+
let rightNodeIndex = node.rightNode;
530+
let rightNode = tree[rightNodeIndex];
531+
nodesToCheck.push(rightNode);
532+
}
533+
return {"nodes": commutativeNodesList,
534+
"parents": commutativeParentsList};
458535
}
459536

460537
// Compares 2 binary trees, and returns true if their contents are equal.

0 commit comments

Comments
 (0)