@@ -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