@@ -488,6 +488,242 @@ var BTree = /** @class */ (function () {
488
488
}
489
489
return { nodequeue : nodequeue , nodeindex : nodeindex , leaf : nextnode } ;
490
490
} ;
491
+ /**
492
+ * Computes the differences between `this` and `other`.
493
+ * For efficiency, the diff is returned via invocations of supplied handlers.
494
+ * The computation is optimized for the case in which the two trees have large amounts
495
+ * of shared data (obtained by calling the `clone` or `with` APIs) and will avoid
496
+ * any iteration of shared state.
497
+ * The handlers can cause computation to early exit by returning {break: R}.
498
+ * @param other The tree to compute a diff against.
499
+ * @param onlyThis Callback invoked for all keys only present in `this`.
500
+ * @param onlyOther Callback invoked for all keys only present in `other`.
501
+ * @param different Callback invoked for all keys with differing values.
502
+ */
503
+ BTree . prototype . diff = function ( other , onlyThis , onlyOther , different ) {
504
+ if ( other . _compare !== this . _compare ) {
505
+ throw new Error ( "Tree comparators are not the same." ) ;
506
+ }
507
+ if ( this . isEmpty || other . isEmpty ) {
508
+ if ( this . isEmpty && other . isEmpty )
509
+ return undefined ;
510
+ // If one tree is empty, everything will be an onlyThis/onlyOther.
511
+ if ( this . isEmpty )
512
+ return onlyOther === undefined ? undefined : BTree . stepToEnd ( BTree . makeDiffCursor ( other ) , onlyOther ) ;
513
+ return onlyThis === undefined ? undefined : BTree . stepToEnd ( BTree . makeDiffCursor ( this ) , onlyThis ) ;
514
+ }
515
+ // Cursor-based diff algorithm is as follows:
516
+ // - Until neither cursor has navigated to the end of the tree, do the following:
517
+ // - If the `this` cursor is "behind" the `other` cursor (strictly <, via compare), advance it.
518
+ // - Otherwise, advance the `other` cursor.
519
+ // - Any time a cursor is stepped, perform the following:
520
+ // - If either cursor points to a key/value pair:
521
+ // - If thisCursor === otherCursor and the values differ, it is a Different.
522
+ // - If thisCursor > otherCursor and otherCursor is at a key/value pair, it is an OnlyOther.
523
+ // - If thisCursor < otherCursor and thisCursor is at a key/value pair, it is an OnlyThis as long as the most recent
524
+ // cursor step was *not* otherCursor advancing from a tie. The extra condition avoids erroneous OnlyOther calls
525
+ // that would occur due to otherCursor being the "leader".
526
+ // - Otherwise, if both cursors point to nodes, compare them. If they are equal by reference (shared), skip
527
+ // both cursors to the next node in the walk.
528
+ // - Once one cursor has finished stepping, any remaining steps (if any) are taken and key/value pairs are logged
529
+ // as OnlyOther (if otherCursor is stepping) or OnlyThis (if thisCursor is stepping).
530
+ // This algorithm gives the critical guarantee that all locations (both nodes and key/value pairs) in both trees that
531
+ // are identical by value (and possibly by reference) will be visited *at the same time* by the cursors.
532
+ // This removes the possibility of emitting incorrect diffs, as well as allowing for skipping shared nodes.
533
+ var _compare = this . _compare ;
534
+ var thisCursor = BTree . makeDiffCursor ( this ) ;
535
+ var otherCursor = BTree . makeDiffCursor ( other ) ;
536
+ // It doesn't matter how thisSteppedLast is initialized.
537
+ // Step order is only used when either cursor is at a leaf, and cursors always start at a node.
538
+ var thisSuccess = true , otherSuccess = true , prevCursorOrder = BTree . compare ( thisCursor , otherCursor , _compare ) ;
539
+ while ( thisSuccess && otherSuccess ) {
540
+ var cursorOrder = BTree . compare ( thisCursor , otherCursor , _compare ) ;
541
+ var thisLeaf = thisCursor . leaf , thisInternalSpine = thisCursor . internalSpine , thisLevelIndices = thisCursor . levelIndices ;
542
+ var otherLeaf = otherCursor . leaf , otherInternalSpine = otherCursor . internalSpine , otherLevelIndices = otherCursor . levelIndices ;
543
+ if ( thisLeaf || otherLeaf ) {
544
+ // If the cursors were at the same location last step, then there is no work to be done.
545
+ if ( prevCursorOrder !== 0 ) {
546
+ if ( cursorOrder === 0 ) {
547
+ if ( thisLeaf && otherLeaf && different ) {
548
+ // Equal keys, check for modifications
549
+ var valThis = thisLeaf . values [ thisLevelIndices [ thisLevelIndices . length - 1 ] ] ;
550
+ var valOther = otherLeaf . values [ otherLevelIndices [ otherLevelIndices . length - 1 ] ] ;
551
+ if ( ! Object . is ( valThis , valOther ) ) {
552
+ var result = different ( thisCursor . currentKey , valThis , valOther ) ;
553
+ if ( result && ( result === null || result === void 0 ? void 0 : result . break ) )
554
+ return result . break ;
555
+ }
556
+ }
557
+ }
558
+ else if ( cursorOrder > 0 ) {
559
+ // If this is the case, we know that either:
560
+ // 1. otherCursor stepped last from a starting position that trailed thisCursor, and is still behind, or
561
+ // 2. thisCursor stepped last and leapfrogged otherCursor
562
+ // Either of these cases is an "only other"
563
+ if ( otherLeaf && onlyOther ) {
564
+ var otherVal = otherLeaf . values [ otherLevelIndices [ otherLevelIndices . length - 1 ] ] ;
565
+ var result = onlyOther ( otherCursor . currentKey , otherVal ) ;
566
+ if ( result && result . break )
567
+ return result . break ;
568
+ }
569
+ }
570
+ else if ( onlyThis ) {
571
+ if ( thisLeaf && prevCursorOrder !== 0 ) {
572
+ var valThis = thisLeaf . values [ thisLevelIndices [ thisLevelIndices . length - 1 ] ] ;
573
+ var result = onlyThis ( thisCursor . currentKey , valThis ) ;
574
+ if ( result && result . break )
575
+ return result . break ;
576
+ }
577
+ }
578
+ }
579
+ }
580
+ else if ( ! thisLeaf && ! otherLeaf && cursorOrder === 0 ) {
581
+ var lastThis = thisInternalSpine . length - 1 ;
582
+ var lastOther = otherInternalSpine . length - 1 ;
583
+ var nodeThis = thisInternalSpine [ lastThis ] [ thisLevelIndices [ lastThis ] ] ;
584
+ var nodeOther = otherInternalSpine [ lastOther ] [ otherLevelIndices [ lastOther ] ] ;
585
+ if ( nodeOther === nodeThis ) {
586
+ prevCursorOrder = 0 ;
587
+ thisSuccess = BTree . step ( thisCursor , true ) ;
588
+ otherSuccess = BTree . step ( otherCursor , true ) ;
589
+ continue ;
590
+ }
591
+ }
592
+ prevCursorOrder = cursorOrder ;
593
+ if ( cursorOrder < 0 ) {
594
+ thisSuccess = BTree . step ( thisCursor ) ;
595
+ }
596
+ else {
597
+ otherSuccess = BTree . step ( otherCursor ) ;
598
+ }
599
+ }
600
+ if ( thisSuccess && onlyThis )
601
+ return BTree . finishCursorWalk ( thisCursor , otherCursor , _compare , onlyThis ) ;
602
+ if ( otherSuccess && onlyOther )
603
+ return BTree . finishCursorWalk ( otherCursor , thisCursor , _compare , onlyOther ) ;
604
+ } ;
605
+ BTree . finishCursorWalk = function ( cursor , cursorFinished , compareKeys , callback ) {
606
+ var compared = BTree . compare ( cursor , cursorFinished , compareKeys ) ;
607
+ if ( compared === 0 ) {
608
+ if ( ! BTree . step ( cursor ) )
609
+ return undefined ;
610
+ }
611
+ else if ( compared < 0 ) {
612
+ check ( false , "cursor walk terminated early" ) ;
613
+ }
614
+ return BTree . stepToEnd ( cursor , callback ) ;
615
+ } ;
616
+ BTree . stepToEnd = function ( cursor , callback ) {
617
+ var canStep = true ;
618
+ while ( canStep ) {
619
+ var leaf = cursor . leaf , levelIndices = cursor . levelIndices , currentKey = cursor . currentKey ;
620
+ if ( leaf ) {
621
+ var value = leaf . values [ levelIndices [ levelIndices . length - 1 ] ] ;
622
+ var result = callback ( currentKey , value ) ;
623
+ if ( result && result . break )
624
+ return result . break ;
625
+ }
626
+ canStep = BTree . step ( cursor ) ;
627
+ }
628
+ return undefined ;
629
+ } ;
630
+ BTree . makeDiffCursor = function ( tree ) {
631
+ var _root = tree . _root , height = tree . height ;
632
+ return { height : height , internalSpine : [ [ _root ] ] , levelIndices : [ 0 ] , leaf : undefined , currentKey : _root . maxKey ( ) } ;
633
+ } ;
634
+ /**
635
+ * Advances the cursor to the next step in the walk of its tree.
636
+ * Cursors are walked backwards in sort order, as this allows them to leverage maxKey() in order to be compared in O(1).
637
+ * @param cursor The cursor to step
638
+ * @param stepToNode If true, the cursor will be advanced to the next node (skipping values)
639
+ * @returns true if the step was completed and false if the step would have caused the cursor to move beyond the end of the tree.
640
+ */
641
+ BTree . step = function ( cursor , stepToNode ) {
642
+ if ( stepToNode === void 0 ) { stepToNode = false ; }
643
+ var internalSpine = cursor . internalSpine , levelIndices = cursor . levelIndices , leaf = cursor . leaf ;
644
+ if ( stepToNode || leaf ) {
645
+ var levelsLength = levelIndices . length ;
646
+ // Step to the next node only if:
647
+ // - We are explicitly directed to via stepToNode, or
648
+ // - There are no key/value pairs left to step to in this leaf
649
+ if ( stepToNode || levelIndices [ levelsLength - 1 ] === 0 ) {
650
+ var spineLength = internalSpine . length ;
651
+ // Root is leaf
652
+ if ( spineLength === 0 )
653
+ return false ;
654
+ // Walk back up the tree until we find a new subtree to descend into
655
+ var nodeLevelIndex = spineLength - 1 ;
656
+ var levelIndexWalkBack = nodeLevelIndex ;
657
+ while ( levelIndexWalkBack >= 0 ) {
658
+ var childIndex = levelIndices [ levelIndexWalkBack ] ;
659
+ if ( childIndex > 0 ) {
660
+ if ( levelIndexWalkBack < levelsLength - 1 ) {
661
+ // Remove leaf state from cursor
662
+ cursor . leaf = undefined ;
663
+ levelIndices . splice ( levelIndexWalkBack + 1 , levelsLength - levelIndexWalkBack ) ;
664
+ }
665
+ // If we walked upwards past any internal node, splice them out
666
+ if ( levelIndexWalkBack < nodeLevelIndex )
667
+ internalSpine . splice ( levelIndexWalkBack + 1 , spineLength - levelIndexWalkBack ) ;
668
+ // Move to new internal node
669
+ var nodeIndex = -- levelIndices [ levelIndexWalkBack ] ;
670
+ cursor . currentKey = internalSpine [ levelIndexWalkBack ] [ nodeIndex ] . maxKey ( ) ;
671
+ return true ;
672
+ }
673
+ levelIndexWalkBack -- ;
674
+ }
675
+ // Cursor is in the far left leaf of the tree, no more nodes to enumerate
676
+ return false ;
677
+ }
678
+ else {
679
+ // Move to new leaf value
680
+ var valueIndex = -- levelIndices [ levelsLength - 1 ] ;
681
+ cursor . currentKey = leaf . keys [ valueIndex ] ;
682
+ return true ;
683
+ }
684
+ }
685
+ else { // Cursor does not point to a value in a leaf, so move downwards
686
+ var nextLevel = internalSpine . length ;
687
+ var currentLevel = nextLevel - 1 ;
688
+ var node = internalSpine [ currentLevel ] [ levelIndices [ currentLevel ] ] ;
689
+ if ( node . isLeaf ) {
690
+ // Entering into a leaf. Set the cursor to point at the last key/value pair.
691
+ cursor . leaf = node ;
692
+ var valueIndex = levelIndices [ nextLevel ] = node . values . length - 1 ;
693
+ cursor . currentKey = node . keys [ valueIndex ] ;
694
+ }
695
+ else {
696
+ var children = node . children ;
697
+ internalSpine [ nextLevel ] = children ;
698
+ var childIndex = children . length - 1 ;
699
+ levelIndices [ nextLevel ] = childIndex ;
700
+ cursor . currentKey = children [ childIndex ] . maxKey ( ) ;
701
+ }
702
+ return true ;
703
+ }
704
+ } ;
705
+ /**
706
+ * Compares the two cursors. Returns a value indicating which cursor is ahead in a walk.
707
+ * Note that cursors are advanced in reverse sorting order.
708
+ */
709
+ BTree . compare = function ( cursorA , cursorB , compareKeys ) {
710
+ var heightA = cursorA . height , currentKeyA = cursorA . currentKey , levelIndicesA = cursorA . levelIndices ;
711
+ var heightB = cursorB . height , currentKeyB = cursorB . currentKey , levelIndicesB = cursorB . levelIndices ;
712
+ // Reverse the comparison order, as cursors are advanced in reverse sorting order
713
+ var keyComparison = compareKeys ( currentKeyB , currentKeyA ) ;
714
+ if ( keyComparison !== 0 ) {
715
+ return keyComparison ;
716
+ }
717
+ // Normalize depth values relative to the shortest tree.
718
+ // This ensures that concurrent cursor walks of trees of differing heights can reliably land on shared nodes at the same time.
719
+ // To accomplish this, a cursor that is on an internal node at depth D1 with maxKey X is considered "behind" a cursor on an
720
+ // internal node at depth D2 with maxKey Y, when D1 < D2. Thus, always walking the cursor that is "behind" will allow the cursor
721
+ // at shallower depth (but equal maxKey) to "catch up" and land on shared nodes.
722
+ var heightMin = heightA < heightB ? heightA : heightB ;
723
+ var depthANormalized = levelIndicesA . length - ( heightA - heightMin ) ;
724
+ var depthBNormalized = levelIndicesB . length - ( heightB - heightMin ) ;
725
+ return depthANormalized - depthBNormalized ;
726
+ } ;
491
727
/** Returns a new iterator for iterating the keys of each pair in ascending order.
492
728
* @param firstKey: Minimum key to include in the output. */
493
729
BTree . prototype . keys = function ( firstKey ) {
@@ -736,9 +972,13 @@ var BTree = /** @class */ (function () {
736
972
/** Gets the height of the tree: the number of internal nodes between the
737
973
* BTree object and its leaf nodes (zero if there are no internal nodes). */
738
974
get : function ( ) {
739
- for ( var node = this . _root , h = - 1 ; node != null ; h ++ )
740
- node = node . children ;
741
- return h ;
975
+ var node = this . _root ;
976
+ var height = - 1 ;
977
+ while ( node ) {
978
+ height ++ ;
979
+ node = node . isLeaf ? undefined : node . children [ 0 ] ;
980
+ }
981
+ return height ;
742
982
} ,
743
983
enumerable : false ,
744
984
configurable : true
0 commit comments