data-structure-typed 2.2.5 → 2.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/CONTRIBUTING.md +47 -1
- package/README.md +6 -6
- package/dist/cjs/index.cjs +100 -83
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs-legacy/index.cjs +100 -83
- package/dist/cjs-legacy/index.cjs.map +1 -1
- package/dist/esm/index.mjs +100 -83
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm-legacy/index.mjs +100 -83
- package/dist/esm-legacy/index.mjs.map +1 -1
- package/dist/types/data-structures/base/iterable-entry-base.d.ts +6 -0
- package/dist/types/data-structures/binary-tree/binary-tree.d.ts +14 -57
- package/dist/types/data-structures/binary-tree/bst.d.ts +46 -126
- package/dist/umd/data-structure-typed.js +100 -83
- package/dist/umd/data-structure-typed.js.map +1 -1
- package/dist/umd/data-structure-typed.min.js +3 -3
- package/dist/umd/data-structure-typed.min.js.map +1 -1
- package/package.json +5 -3
- package/src/common/index.ts +2 -4
- package/src/data-structures/base/iterable-entry-base.ts +9 -0
- package/src/data-structures/binary-tree/binary-tree.ts +67 -0
- package/src/data-structures/binary-tree/bst.ts +295 -89
- package/test/unit/data-structures/binary-tree/bst.test.ts +1138 -525
- package/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +12 -10
- package/tsup.config.js +6 -0
- package/tsup.leetcode.config.js +3 -0
- package/tsup.node.config.js +12 -0
|
@@ -492,7 +492,6 @@ describe('BST operations test', () => {
|
|
|
492
492
|
it('should search in range', () => {
|
|
493
493
|
const bst = new BST<number>([10, 5, 15, 3, 7, 12, 18]);
|
|
494
494
|
expect(bst.rangeSearch([4, 12])).toEqual([5, 7, 10, 12]);
|
|
495
|
-
expect(() => bst.rangeSearch([12, 4])).toThrow('low must be less than or equal to high');
|
|
496
495
|
expect(bst.rangeSearch([12, 12])).toEqual([12]);
|
|
497
496
|
});
|
|
498
497
|
});
|
|
@@ -1613,7 +1612,7 @@ describe('BST _keyValueNodeOrEntryToNodeAndValue edge', () => {
|
|
|
1613
1612
|
});
|
|
1614
1613
|
});
|
|
1615
1614
|
|
|
1616
|
-
describe('BST
|
|
1615
|
+
describe('BST ceiling and higher', () => {
|
|
1617
1616
|
let bst: BST<number, string>;
|
|
1618
1617
|
|
|
1619
1618
|
beforeEach(() => {
|
|
@@ -1621,184 +1620,184 @@ describe('BST lowerBound and upperBound', () => {
|
|
|
1621
1620
|
bst = new BST<number, string>([10, 5, 15, 3, 7, 12, 20, 1, 4, 6, 8, 11, 13, 18, 25]);
|
|
1622
1621
|
});
|
|
1623
1622
|
|
|
1624
|
-
describe('
|
|
1625
|
-
it('should return the
|
|
1623
|
+
describe('ceiling', () => {
|
|
1624
|
+
it('should return the key with exact key match', () => {
|
|
1626
1625
|
// Test for key that exists in tree
|
|
1627
|
-
const
|
|
1628
|
-
expect(
|
|
1629
|
-
expect(
|
|
1626
|
+
const key = bst.ceiling(10);
|
|
1627
|
+
expect(key).toBeDefined();
|
|
1628
|
+
expect(key).toBe(10);
|
|
1630
1629
|
});
|
|
1631
1630
|
|
|
1632
|
-
it('should return the smallest
|
|
1631
|
+
it('should return the smallest key >= key when exact match not found', () => {
|
|
1633
1632
|
// Test for key 9 (doesn't exist, should return 10)
|
|
1634
|
-
const
|
|
1635
|
-
expect(
|
|
1636
|
-
expect(
|
|
1633
|
+
const key = bst.ceiling(9);
|
|
1634
|
+
expect(key).toBeDefined();
|
|
1635
|
+
expect(key).toBe(10);
|
|
1637
1636
|
});
|
|
1638
1637
|
|
|
1639
|
-
it('should return
|
|
1638
|
+
it('should return key with key >= search key from multiple candidates', () => {
|
|
1640
1639
|
// Test for key 6 (exists, should return 6)
|
|
1641
|
-
const
|
|
1642
|
-
expect(
|
|
1643
|
-
expect(
|
|
1640
|
+
const key = bst.ceiling(6);
|
|
1641
|
+
expect(key).toBeDefined();
|
|
1642
|
+
expect(key).toBe(6);
|
|
1644
1643
|
|
|
1645
1644
|
// Test for key 7 (exists, should return 7)
|
|
1646
|
-
const
|
|
1647
|
-
expect(
|
|
1648
|
-
expect(
|
|
1645
|
+
const key2 = bst.ceiling(7);
|
|
1646
|
+
expect(key2).toBeDefined();
|
|
1647
|
+
expect(key2).toBe(7);
|
|
1649
1648
|
});
|
|
1650
1649
|
|
|
1651
1650
|
it('should return smallest key >= search key for non-existent key in middle range', () => {
|
|
1652
1651
|
// Test for key 14 (doesn't exist, should return 15)
|
|
1653
|
-
const
|
|
1654
|
-
expect(
|
|
1655
|
-
expect(
|
|
1652
|
+
const key = bst.ceiling(14);
|
|
1653
|
+
expect(key).toBeDefined();
|
|
1654
|
+
expect(key).toBe(15);
|
|
1656
1655
|
|
|
1657
1656
|
// Test for key 11.5 (doesn't exist, should return 12)
|
|
1658
|
-
const
|
|
1659
|
-
expect(
|
|
1660
|
-
expect(
|
|
1657
|
+
const key2 = bst.ceiling(11.5);
|
|
1658
|
+
expect(key2).toBeDefined();
|
|
1659
|
+
expect(key2).toBe(12);
|
|
1661
1660
|
});
|
|
1662
1661
|
|
|
1663
1662
|
it('should return smallest key when search key is less than minimum', () => {
|
|
1664
1663
|
// Test for key 0 (should return 1, the minimum)
|
|
1665
|
-
const
|
|
1666
|
-
expect(
|
|
1667
|
-
expect(
|
|
1664
|
+
const key = bst.ceiling(0);
|
|
1665
|
+
expect(key).toBeDefined();
|
|
1666
|
+
expect(key).toBe(1);
|
|
1668
1667
|
|
|
1669
1668
|
// Test for key -100 (should return 1, the minimum)
|
|
1670
|
-
const
|
|
1671
|
-
expect(
|
|
1672
|
-
expect(
|
|
1669
|
+
const key2 = bst.ceiling(-100);
|
|
1670
|
+
expect(key2).toBeDefined();
|
|
1671
|
+
expect(key2).toBe(1);
|
|
1673
1672
|
});
|
|
1674
1673
|
|
|
1675
1674
|
it('should return undefined when search key is greater than maximum', () => {
|
|
1676
1675
|
// Test for key 100 (no key >= 100, should return undefined)
|
|
1677
|
-
const
|
|
1678
|
-
expect(
|
|
1676
|
+
const key = bst.ceiling(100);
|
|
1677
|
+
expect(key).toBeUndefined();
|
|
1679
1678
|
|
|
1680
1679
|
// Test for key 26 (no key >= 26, should return undefined)
|
|
1681
|
-
const
|
|
1682
|
-
expect(
|
|
1680
|
+
const key2 = bst.ceiling(26);
|
|
1681
|
+
expect(key2).toBeUndefined();
|
|
1683
1682
|
});
|
|
1684
1683
|
|
|
1685
|
-
it('should return correct
|
|
1684
|
+
it('should return correct key for edge cases', () => {
|
|
1686
1685
|
// Test for key 25 (maximum, should return 25)
|
|
1687
|
-
const
|
|
1688
|
-
expect(
|
|
1689
|
-
expect(
|
|
1686
|
+
const key = bst.ceiling(25);
|
|
1687
|
+
expect(key).toBeDefined();
|
|
1688
|
+
expect(key).toBe(25);
|
|
1690
1689
|
|
|
1691
1690
|
// Test for key 1 (minimum, should return 1)
|
|
1692
|
-
const
|
|
1693
|
-
expect(
|
|
1694
|
-
expect(
|
|
1691
|
+
const key2 = bst.ceiling(1);
|
|
1692
|
+
expect(key2).toBeDefined();
|
|
1693
|
+
expect(key2).toBe(1);
|
|
1695
1694
|
});
|
|
1696
1695
|
});
|
|
1697
1696
|
|
|
1698
|
-
describe('
|
|
1697
|
+
describe('higher', () => {
|
|
1699
1698
|
it('should return the smallest key > search key', () => {
|
|
1700
1699
|
// Test for key 10 (exists, should return next key > 10, which is 11)
|
|
1701
|
-
const
|
|
1702
|
-
expect(
|
|
1703
|
-
expect(
|
|
1700
|
+
const key = bst.higher(10);
|
|
1701
|
+
expect(key).toBeDefined();
|
|
1702
|
+
expect(key).toBe(11);
|
|
1704
1703
|
});
|
|
1705
1704
|
|
|
1706
1705
|
it('should return the smallest key > search key for non-existent key', () => {
|
|
1707
1706
|
// Test for key 9 (doesn't exist, should return 10, the smallest key > 9)
|
|
1708
|
-
const
|
|
1709
|
-
expect(
|
|
1710
|
-
expect(
|
|
1707
|
+
const key = bst.higher(9);
|
|
1708
|
+
expect(key).toBeDefined();
|
|
1709
|
+
expect(key).toBe(10);
|
|
1711
1710
|
|
|
1712
1711
|
// Test for key 14 (doesn't exist, should return 15)
|
|
1713
|
-
const
|
|
1714
|
-
expect(
|
|
1715
|
-
expect(
|
|
1712
|
+
const key2 = bst.higher(14);
|
|
1713
|
+
expect(key2).toBeDefined();
|
|
1714
|
+
expect(key2).toBe(15);
|
|
1716
1715
|
});
|
|
1717
1716
|
|
|
1718
1717
|
it('should skip equal keys and return the next larger key', () => {
|
|
1719
1718
|
// Test for key 5 (exists, should return 6, the smallest key > 5)
|
|
1720
|
-
const
|
|
1721
|
-
expect(
|
|
1722
|
-
expect(
|
|
1719
|
+
const key = bst.higher(5);
|
|
1720
|
+
expect(key).toBeDefined();
|
|
1721
|
+
expect(key).toBe(6);
|
|
1723
1722
|
|
|
1724
1723
|
// Test for key 20 (exists, should return 25)
|
|
1725
|
-
const
|
|
1726
|
-
expect(
|
|
1727
|
-
expect(
|
|
1724
|
+
const key2 = bst.higher(20);
|
|
1725
|
+
expect(key2).toBeDefined();
|
|
1726
|
+
expect(key2).toBe(25);
|
|
1728
1727
|
});
|
|
1729
1728
|
|
|
1730
1729
|
it('should return smallest key > search key for non-existent key in middle range', () => {
|
|
1731
1730
|
// Test for key 11.5 (doesn't exist, should return 12)
|
|
1732
|
-
const
|
|
1733
|
-
expect(
|
|
1734
|
-
expect(
|
|
1731
|
+
const key = bst.higher(11.5);
|
|
1732
|
+
expect(key).toBeDefined();
|
|
1733
|
+
expect(key).toBe(12);
|
|
1735
1734
|
|
|
1736
1735
|
// Test for key 19 (doesn't exist, should return 20)
|
|
1737
|
-
const
|
|
1738
|
-
expect(
|
|
1739
|
-
expect(
|
|
1736
|
+
const key2 = bst.higher(19);
|
|
1737
|
+
expect(key2).toBeDefined();
|
|
1738
|
+
expect(key2).toBe(20);
|
|
1740
1739
|
});
|
|
1741
1740
|
|
|
1742
1741
|
it('should return smallest key when search key is less than minimum', () => {
|
|
1743
1742
|
// Test for key 0 (should return 1, the minimum)
|
|
1744
|
-
const
|
|
1745
|
-
expect(
|
|
1746
|
-
expect(
|
|
1743
|
+
const key = bst.higher(0);
|
|
1744
|
+
expect(key).toBeDefined();
|
|
1745
|
+
expect(key).toBe(1);
|
|
1747
1746
|
|
|
1748
1747
|
// Test for key -100 (should return 1, the minimum)
|
|
1749
|
-
const
|
|
1750
|
-
expect(
|
|
1751
|
-
expect(
|
|
1748
|
+
const key2 = bst.higher(-100);
|
|
1749
|
+
expect(key2).toBeDefined();
|
|
1750
|
+
expect(key2).toBe(1);
|
|
1752
1751
|
});
|
|
1753
1752
|
|
|
1754
1753
|
it('should return undefined when search key is >= maximum', () => {
|
|
1755
1754
|
// Test for key 100 (no key > 100, should return undefined)
|
|
1756
|
-
const
|
|
1757
|
-
expect(
|
|
1755
|
+
const key = bst.higher(100);
|
|
1756
|
+
expect(key).toBeUndefined();
|
|
1758
1757
|
|
|
1759
1758
|
// Test for key 25 (maximum, no key > 25, should return undefined)
|
|
1760
|
-
const
|
|
1761
|
-
expect(
|
|
1759
|
+
const key2 = bst.higher(25);
|
|
1760
|
+
expect(key2).toBeUndefined();
|
|
1762
1761
|
|
|
1763
1762
|
// Test for key 26 (no key > 26, should return undefined)
|
|
1764
|
-
const
|
|
1765
|
-
expect(
|
|
1763
|
+
const key3 = bst.higher(26);
|
|
1764
|
+
expect(key3).toBeUndefined();
|
|
1766
1765
|
});
|
|
1767
1766
|
|
|
1768
|
-
it('should return correct
|
|
1767
|
+
it('should return correct key for edge cases', () => {
|
|
1769
1768
|
// Test for key 1 (minimum, should return next key 3)
|
|
1770
|
-
const
|
|
1771
|
-
expect(
|
|
1772
|
-
expect(
|
|
1769
|
+
const key = bst.higher(1);
|
|
1770
|
+
expect(key).toBeDefined();
|
|
1771
|
+
expect(key).toBe(3);
|
|
1773
1772
|
|
|
1774
1773
|
// Test for key 24 (doesn't exist, should return 25)
|
|
1775
|
-
const
|
|
1776
|
-
expect(
|
|
1777
|
-
expect(
|
|
1774
|
+
const key2 = bst.higher(24);
|
|
1775
|
+
expect(key2).toBeDefined();
|
|
1776
|
+
expect(key2).toBe(25);
|
|
1778
1777
|
});
|
|
1779
1778
|
});
|
|
1780
1779
|
|
|
1781
|
-
describe('
|
|
1782
|
-
it('should demonstrate difference between
|
|
1780
|
+
describe('ceiling vs higher comparison', () => {
|
|
1781
|
+
it('should demonstrate difference between ceiling and higher', () => {
|
|
1783
1782
|
const searchKey = 12;
|
|
1784
1783
|
|
|
1785
|
-
//
|
|
1786
|
-
const lower = bst.
|
|
1787
|
-
expect(lower
|
|
1784
|
+
// ceiling(12) should return 12 (key >= 12)
|
|
1785
|
+
const lower = bst.ceiling(searchKey);
|
|
1786
|
+
expect(lower).toBe(12);
|
|
1788
1787
|
|
|
1789
|
-
//
|
|
1790
|
-
const upper = bst.
|
|
1791
|
-
expect(upper
|
|
1788
|
+
// higher(12) should return 13 (key > 12)
|
|
1789
|
+
const upper = bst.higher(searchKey);
|
|
1790
|
+
expect(upper).toBe(13);
|
|
1792
1791
|
});
|
|
1793
1792
|
|
|
1794
1793
|
it('should return same result for non-existent key in both methods', () => {
|
|
1795
1794
|
const searchKey = 11.5;
|
|
1796
1795
|
|
|
1797
|
-
// Both should return 12 (smallest key >= 11.5 for
|
|
1798
|
-
const lower = bst.
|
|
1799
|
-
const upper = bst.
|
|
1800
|
-
expect(lower
|
|
1801
|
-
expect(upper
|
|
1796
|
+
// Both should return 12 (smallest key >= 11.5 for ceiling, smallest key > 11.5 for higher)
|
|
1797
|
+
const lower = bst.ceiling(searchKey);
|
|
1798
|
+
const upper = bst.higher(searchKey);
|
|
1799
|
+
expect(lower).toBe(12);
|
|
1800
|
+
expect(upper).toBe(12);
|
|
1802
1801
|
});
|
|
1803
1802
|
});
|
|
1804
1803
|
|
|
@@ -1825,90 +1824,90 @@ describe('BST lowerBound and upperBound', () => {
|
|
|
1825
1824
|
keys.forEach(k => bst.add(k, `val-${k}`));
|
|
1826
1825
|
});
|
|
1827
1826
|
|
|
1828
|
-
describe('
|
|
1827
|
+
describe('ceiling (First key >= key)', () => {
|
|
1829
1828
|
test('should return strict match if key exists', () => {
|
|
1830
1829
|
// Case: Key exists
|
|
1831
|
-
expect(bst.
|
|
1832
|
-
expect(bst.
|
|
1833
|
-
expect(bst.
|
|
1830
|
+
expect(bst.ceiling(10)).toBe(10);
|
|
1831
|
+
expect(bst.ceiling(6)).toBe(6);
|
|
1832
|
+
expect(bst.ceiling(20)).toBe(20);
|
|
1834
1833
|
});
|
|
1835
1834
|
|
|
1836
|
-
test('should return next larger
|
|
1835
|
+
test('should return next larger key if key does not exist', () => {
|
|
1837
1836
|
// Case: Key doesn't exist, but falls within range
|
|
1838
|
-
expect(bst.
|
|
1839
|
-
expect(bst.
|
|
1840
|
-
expect(bst.
|
|
1837
|
+
expect(bst.ceiling(7)).toBe(8); // 7 -> 8
|
|
1838
|
+
expect(bst.ceiling(11)).toBe(12); // 11 -> 12
|
|
1839
|
+
expect(bst.ceiling(3)).toBe(5); // 3 -> 5
|
|
1841
1840
|
});
|
|
1842
1841
|
|
|
1843
|
-
test('should return smallest
|
|
1842
|
+
test('should return smallest key if key is smaller than min', () => {
|
|
1844
1843
|
// Case: Key is smaller than the minimum value in the tree
|
|
1845
|
-
expect(bst.
|
|
1846
|
-
expect(bst.
|
|
1844
|
+
expect(bst.ceiling(0)).toBe(2);
|
|
1845
|
+
expect(bst.ceiling(-100)).toBe(2);
|
|
1847
1846
|
});
|
|
1848
1847
|
|
|
1849
1848
|
test('should return undefined if key is larger than max', () => {
|
|
1850
1849
|
// Case: Key is larger than the maximum value in the tree
|
|
1851
|
-
expect(bst.
|
|
1852
|
-
expect(bst.
|
|
1850
|
+
expect(bst.ceiling(21)).toBeUndefined();
|
|
1851
|
+
expect(bst.ceiling(100)).toBeUndefined();
|
|
1853
1852
|
});
|
|
1854
1853
|
|
|
1855
1854
|
test('should work with IterationType.RECURSIVE explicitly', () => {
|
|
1856
|
-
expect(bst.
|
|
1857
|
-
expect(bst.
|
|
1858
|
-
expect(bst.
|
|
1855
|
+
expect(bst.ceiling(7)).toBe(8);
|
|
1856
|
+
expect(bst.ceiling(10)).toBe(10);
|
|
1857
|
+
expect(bst.ceiling(21)).toBeUndefined();
|
|
1859
1858
|
});
|
|
1860
1859
|
|
|
1861
1860
|
test('should work with IterationType.ITERATIVE explicitly', () => {
|
|
1862
|
-
expect(bst.
|
|
1863
|
-
expect(bst.
|
|
1864
|
-
expect(bst.
|
|
1861
|
+
expect(bst.ceiling(7)).toBe(8);
|
|
1862
|
+
expect(bst.ceiling(10)).toBe(10);
|
|
1863
|
+
expect(bst.ceiling(21)).toBeUndefined();
|
|
1865
1864
|
});
|
|
1866
1865
|
});
|
|
1867
1866
|
|
|
1868
|
-
describe('
|
|
1869
|
-
test('should return next larger
|
|
1867
|
+
describe('higher (First key > key)', () => {
|
|
1868
|
+
test('should return next larger key even if key exists', () => {
|
|
1870
1869
|
// Case: Key exists, but we want strictly greater
|
|
1871
|
-
expect(bst.
|
|
1872
|
-
expect(bst.
|
|
1873
|
-
expect(bst.
|
|
1870
|
+
expect(bst.higher(10)).toBe(12); // > 10 is 12
|
|
1871
|
+
expect(bst.higher(6)).toBe(8); // > 6 is 8
|
|
1872
|
+
expect(bst.higher(20)).toBeUndefined(); // > 20 is undefined
|
|
1874
1873
|
});
|
|
1875
1874
|
|
|
1876
|
-
test('should return next larger
|
|
1875
|
+
test('should return next larger key if key does not exist', () => {
|
|
1877
1876
|
// Case: Key doesn't exist
|
|
1878
|
-
expect(bst.
|
|
1879
|
-
expect(bst.
|
|
1877
|
+
expect(bst.higher(7)).toBe(8);
|
|
1878
|
+
expect(bst.higher(11)).toBe(12);
|
|
1880
1879
|
});
|
|
1881
1880
|
|
|
1882
1881
|
test('should return undefined if key is larger than or equal to max', () => {
|
|
1883
|
-
expect(bst.
|
|
1884
|
-
expect(bst.
|
|
1882
|
+
expect(bst.higher(20)).toBeUndefined();
|
|
1883
|
+
expect(bst.higher(21)).toBeUndefined();
|
|
1885
1884
|
});
|
|
1886
1885
|
|
|
1887
1886
|
test('should work with IterationType.RECURSIVE explicitly', () => {
|
|
1888
|
-
expect(bst.
|
|
1889
|
-
expect(bst.
|
|
1887
|
+
expect(bst.higher(10)).toBe(12);
|
|
1888
|
+
expect(bst.higher(20)).toBeUndefined();
|
|
1890
1889
|
});
|
|
1891
1890
|
});
|
|
1892
1891
|
|
|
1893
1892
|
describe('Edge Cases', () => {
|
|
1894
1893
|
test('should handle empty tree', () => {
|
|
1895
1894
|
const emptyTree = new BST<number, string>();
|
|
1896
|
-
expect(emptyTree.
|
|
1897
|
-
expect(emptyTree.
|
|
1895
|
+
expect(emptyTree.ceiling(10)).toBeUndefined();
|
|
1896
|
+
expect(emptyTree.higher(10)).toBeUndefined();
|
|
1898
1897
|
});
|
|
1899
1898
|
|
|
1900
1899
|
test('should handle single node tree', () => {
|
|
1901
1900
|
const singleNodeTree = new BST<number, string>();
|
|
1902
1901
|
singleNodeTree.add(10);
|
|
1903
1902
|
|
|
1904
|
-
//
|
|
1905
|
-
expect(singleNodeTree.
|
|
1906
|
-
expect(singleNodeTree.
|
|
1907
|
-
expect(singleNodeTree.
|
|
1903
|
+
// ceiling
|
|
1904
|
+
expect(singleNodeTree.ceiling(5)).toBe(10);
|
|
1905
|
+
expect(singleNodeTree.ceiling(10)).toBe(10);
|
|
1906
|
+
expect(singleNodeTree.ceiling(15)).toBeUndefined();
|
|
1908
1907
|
|
|
1909
|
-
//
|
|
1910
|
-
expect(singleNodeTree.
|
|
1911
|
-
expect(singleNodeTree.
|
|
1908
|
+
// higher
|
|
1909
|
+
expect(singleNodeTree.higher(5)).toBe(10);
|
|
1910
|
+
expect(singleNodeTree.higher(10)).toBeUndefined();
|
|
1912
1911
|
});
|
|
1913
1912
|
});
|
|
1914
1913
|
|
|
@@ -1928,9 +1927,9 @@ describe('BST lowerBound and upperBound', () => {
|
|
|
1928
1927
|
const testQueries = generateRandomArray(200, 0, 1000);
|
|
1929
1928
|
|
|
1930
1929
|
testQueries.forEach(queryKey => {
|
|
1931
|
-
// 1. Verify
|
|
1932
|
-
const recLower = fuzzTree.
|
|
1933
|
-
const iterLower = fuzzTree.
|
|
1930
|
+
// 1. Verify ceiling
|
|
1931
|
+
const recLower = fuzzTree.ceiling(queryKey);
|
|
1932
|
+
const iterLower = fuzzTree.ceiling(queryKey);
|
|
1934
1933
|
|
|
1935
1934
|
// Verify consistency between recursive and iterative
|
|
1936
1935
|
expect(recLower).toBe(iterLower);
|
|
@@ -1939,9 +1938,9 @@ describe('BST lowerBound and upperBound', () => {
|
|
|
1939
1938
|
const expectedLower = sortedKeys.find(k => k >= queryKey);
|
|
1940
1939
|
expect(recLower).toBe(expectedLower);
|
|
1941
1940
|
|
|
1942
|
-
// 2. Verify
|
|
1943
|
-
const recUpper = fuzzTree.
|
|
1944
|
-
const iterUpper = fuzzTree.
|
|
1941
|
+
// 2. Verify higher
|
|
1942
|
+
const recUpper = fuzzTree.higher(queryKey);
|
|
1943
|
+
const iterUpper = fuzzTree.higher(queryKey);
|
|
1945
1944
|
|
|
1946
1945
|
// Verify consistency
|
|
1947
1946
|
expect(recUpper).toBe(iterUpper);
|
|
@@ -1959,14 +1958,14 @@ describe('BST lowerBound and upperBound', () => {
|
|
|
1959
1958
|
bst = new BST<number, string>();
|
|
1960
1959
|
});
|
|
1961
1960
|
|
|
1962
|
-
it('
|
|
1963
|
-
const
|
|
1964
|
-
expect(
|
|
1961
|
+
it('ceiling should return undefined on empty tree', () => {
|
|
1962
|
+
const key = bst.ceiling(10);
|
|
1963
|
+
expect(key).toBeUndefined();
|
|
1965
1964
|
});
|
|
1966
1965
|
|
|
1967
|
-
it('
|
|
1968
|
-
const
|
|
1969
|
-
expect(
|
|
1966
|
+
it('higher should return undefined on empty tree', () => {
|
|
1967
|
+
const key = bst.higher(10);
|
|
1968
|
+
expect(key).toBeUndefined();
|
|
1970
1969
|
});
|
|
1971
1970
|
});
|
|
1972
1971
|
|
|
@@ -1976,258 +1975,67 @@ describe('BST lowerBound and upperBound', () => {
|
|
|
1976
1975
|
bst.add(10, 'ten');
|
|
1977
1976
|
});
|
|
1978
1977
|
|
|
1979
|
-
it('
|
|
1980
|
-
const
|
|
1981
|
-
expect(
|
|
1982
|
-
expect(
|
|
1978
|
+
it('ceiling should return the key if key matches', () => {
|
|
1979
|
+
const key = bst.ceiling(10);
|
|
1980
|
+
expect(key).toBeDefined();
|
|
1981
|
+
expect(key).toBe(10);
|
|
1983
1982
|
});
|
|
1984
1983
|
|
|
1985
|
-
it('
|
|
1986
|
-
const
|
|
1987
|
-
expect(
|
|
1988
|
-
expect(
|
|
1984
|
+
it('ceiling should return the key if search key is less', () => {
|
|
1985
|
+
const key = bst.ceiling(5);
|
|
1986
|
+
expect(key).toBeDefined();
|
|
1987
|
+
expect(key).toBe(10);
|
|
1989
1988
|
});
|
|
1990
1989
|
|
|
1991
|
-
it('
|
|
1992
|
-
const
|
|
1993
|
-
expect(
|
|
1990
|
+
it('ceiling should return undefined if search key is greater', () => {
|
|
1991
|
+
const key = bst.ceiling(15);
|
|
1992
|
+
expect(key).toBeUndefined();
|
|
1994
1993
|
});
|
|
1995
1994
|
|
|
1996
|
-
it('
|
|
1997
|
-
const
|
|
1998
|
-
expect(
|
|
1995
|
+
it('higher should return undefined if key matches', () => {
|
|
1996
|
+
const key = bst.higher(10);
|
|
1997
|
+
expect(key).toBeUndefined();
|
|
1999
1998
|
});
|
|
2000
1999
|
|
|
2001
|
-
it('
|
|
2002
|
-
const
|
|
2003
|
-
expect(
|
|
2004
|
-
expect(
|
|
2000
|
+
it('higher should return the key if search key is less', () => {
|
|
2001
|
+
const key = bst.higher(5);
|
|
2002
|
+
expect(key).toBeDefined();
|
|
2003
|
+
expect(key).toBe(10);
|
|
2005
2004
|
});
|
|
2006
2005
|
|
|
2007
|
-
it('
|
|
2008
|
-
const
|
|
2009
|
-
expect(
|
|
2006
|
+
it('higher should return undefined if search key is greater', () => {
|
|
2007
|
+
const key = bst.higher(15);
|
|
2008
|
+
expect(key).toBeUndefined();
|
|
2010
2009
|
});
|
|
2011
2010
|
});
|
|
2012
2011
|
|
|
2013
2012
|
describe('practical LeetCode use case: Range search', () => {
|
|
2014
2013
|
it('should find all keys in range [12, 20]', () => {
|
|
2015
|
-
// Use
|
|
2016
|
-
const start = bst.
|
|
2014
|
+
// Use ceiling to find start of range and higher to find end
|
|
2015
|
+
const start = bst.ceiling(12);
|
|
2017
2016
|
|
|
2018
|
-
|
|
2019
|
-
// We manually add keys we know are in range to verify logic works
|
|
2020
|
-
// The lowerBound(12) returns 12. upperBound(20) returns 25 (or null if max).
|
|
2021
|
-
// Logic: we want >= 12 and <= 20.
|
|
2022
|
-
|
|
2023
|
-
expect(start?.key).toBe(12);
|
|
2017
|
+
expect(start).toBe(12);
|
|
2024
2018
|
|
|
2025
2019
|
// Let's verify we found the correct bounds
|
|
2026
|
-
expect(start
|
|
2020
|
+
expect(start).toBeGreaterThanOrEqual(12);
|
|
2027
2021
|
|
|
2028
2022
|
// Check overlap
|
|
2029
|
-
const rangeStart = bst.
|
|
2030
|
-
const rangeEndInclusive = bst.
|
|
2023
|
+
const rangeStart = bst.ceiling(12);
|
|
2024
|
+
const rangeEndInclusive = bst.ceiling(20); // Should be 20
|
|
2031
2025
|
|
|
2032
|
-
expect(rangeStart
|
|
2033
|
-
expect(rangeEndInclusive
|
|
2026
|
+
expect(rangeStart).toBe(12);
|
|
2027
|
+
expect(rangeEndInclusive).toBe(20);
|
|
2034
2028
|
});
|
|
2035
2029
|
|
|
2036
2030
|
it('should help identify interval overlaps', () => {
|
|
2037
2031
|
// Check if interval [14, 19] overlaps with any existing interval
|
|
2038
|
-
// Use
|
|
2039
|
-
const nextKey = bst.
|
|
2040
|
-
// Use
|
|
2041
|
-
const firstInRange = bst.
|
|
2042
|
-
|
|
2043
|
-
expect(firstInRange?.key).toBe(15);
|
|
2044
|
-
expect(nextKey?.key).toBe(20);
|
|
2045
|
-
});
|
|
2046
|
-
});
|
|
2047
|
-
});
|
|
2048
|
-
|
|
2049
|
-
describe('BST Advanced Bound Methods Tests', () => {
|
|
2050
|
-
let bst: BST<number, string>;
|
|
2051
|
-
|
|
2052
|
-
// Helper: Generate random number array
|
|
2053
|
-
const generateRandomArray = (size: number, min: number, max: number) => {
|
|
2054
|
-
return Array.from({ length: size }, () => Math.floor(Math.random() * (max - min + 1)) + min);
|
|
2055
|
-
};
|
|
2056
|
-
|
|
2057
|
-
beforeEach(() => {
|
|
2058
|
-
// Construct a standard test tree
|
|
2059
|
-
// Structure:
|
|
2060
|
-
// 10
|
|
2061
|
-
// / \
|
|
2062
|
-
// 5 15
|
|
2063
|
-
// / \ / \
|
|
2064
|
-
// 2 8 12 20
|
|
2065
|
-
// / \
|
|
2066
|
-
// 6 9
|
|
2067
|
-
bst = new BST<number, string>();
|
|
2068
|
-
const keys = [10, 5, 15, 2, 8, 12, 20, 6, 9];
|
|
2069
|
-
keys.forEach(k => bst.add(k, `val-${k}`));
|
|
2070
|
-
});
|
|
2071
|
-
|
|
2072
|
-
describe('Polymorphic Input Support', () => {
|
|
2073
|
-
test('should accept raw Key', () => {
|
|
2074
|
-
expect(bst.lowerBound(7)?.key).toBe(8);
|
|
2075
|
-
expect(bst.upperBound(7)?.key).toBe(8);
|
|
2076
|
-
});
|
|
2077
|
-
|
|
2078
|
-
test('should accept BSTNode object', () => {
|
|
2079
|
-
// Find a node first
|
|
2080
|
-
const node5 = bst.getNode(5);
|
|
2081
|
-
expect(node5).toBeDefined();
|
|
2082
|
-
|
|
2083
|
-
// Use the node as input for lowerBound (should return itself)
|
|
2084
|
-
expect(bst.lowerBound(node5)?.key).toBe(5);
|
|
2085
|
-
|
|
2086
|
-
// Use the node as input for upperBound (should return next larger)
|
|
2087
|
-
expect(bst.upperBound(node5)?.key).toBe(6);
|
|
2088
|
-
});
|
|
2089
|
-
|
|
2090
|
-
test('should accept Entry tuple [key, value]', () => {
|
|
2091
|
-
// Input as [key, value] tuple
|
|
2092
|
-
const entry: [number, string] = [7, 'val-7'];
|
|
2093
|
-
|
|
2094
|
-
expect(bst.lowerBound(entry)?.key).toBe(8);
|
|
2095
|
-
expect(bst.upperBound(entry)?.key).toBe(8);
|
|
2096
|
-
|
|
2097
|
-
const existingEntry: [number, string] = [10, 'val-10'];
|
|
2098
|
-
expect(bst.lowerBound(existingEntry)?.key).toBe(10);
|
|
2099
|
-
expect(bst.upperBound(existingEntry)?.key).toBe(12);
|
|
2100
|
-
});
|
|
2101
|
-
|
|
2102
|
-
test('should accept Predicate function (Linear Search Fallback)', () => {
|
|
2103
|
-
// Predicate: Find first node with key > 11 (Expect 12)
|
|
2104
|
-
// Note: Predicate search uses in-order traversal
|
|
2105
|
-
const predicate = (node: any) => node.key > 11;
|
|
2106
|
-
|
|
2107
|
-
// For predicate, lowerBound and upperBound behave identically
|
|
2108
|
-
// (they just return the first match of the predicate)
|
|
2109
|
-
expect(bst.lowerBound(predicate)?.key).toBe(12);
|
|
2110
|
-
expect(bst.upperBound(predicate)?.key).toBe(12);
|
|
2111
|
-
|
|
2112
|
-
// Predicate: Find specific value
|
|
2113
|
-
expect(bst.lowerBound(n => n.key === 6)?.key).toBe(6);
|
|
2114
|
-
});
|
|
2115
|
-
});
|
|
2116
|
-
|
|
2117
|
-
describe('Iteration Types with Polymorphic Inputs', () => {
|
|
2118
|
-
test('should work recursively with all types', () => {
|
|
2119
|
-
const type = 'RECURSIVE';
|
|
2120
|
-
|
|
2121
|
-
expect(bst.lowerBound(7, type)?.key).toBe(8);
|
|
2122
|
-
expect(bst.lowerBound(bst.getNode(5), type)?.key).toBe(5);
|
|
2123
|
-
expect(bst.lowerBound([7, 'val'], type)?.key).toBe(8);
|
|
2124
|
-
expect(bst.lowerBound(n => n.key > 11, type)?.key).toBe(12);
|
|
2125
|
-
});
|
|
2126
|
-
|
|
2127
|
-
test('should work iteratively with all types', () => {
|
|
2128
|
-
const type = 'ITERATIVE';
|
|
2129
|
-
|
|
2130
|
-
expect(bst.lowerBound(7, type)?.key).toBe(8);
|
|
2131
|
-
expect(bst.lowerBound(bst.getNode(5), type)?.key).toBe(5);
|
|
2132
|
-
expect(bst.lowerBound([7, 'val'], type)?.key).toBe(8);
|
|
2133
|
-
expect(bst.lowerBound(n => n.key > 11, type)?.key).toBe(12);
|
|
2134
|
-
});
|
|
2135
|
-
});
|
|
2136
|
-
|
|
2137
|
-
describe('Complex Edge Cases', () => {
|
|
2138
|
-
test('should return undefined for null/undefined input', () => {
|
|
2139
|
-
expect(bst.lowerBound(null)).toBeUndefined();
|
|
2140
|
-
expect(bst.lowerBound(undefined)).toBeUndefined();
|
|
2141
|
-
});
|
|
2142
|
-
|
|
2143
|
-
test('should handle predicate returning false for all nodes', () => {
|
|
2144
|
-
expect(bst.lowerBound(n => n.key > 100)).toBeUndefined();
|
|
2145
|
-
});
|
|
2146
|
-
});
|
|
2147
|
-
|
|
2148
|
-
describe('Consistency Check (Fuzz Testing)', () => {
|
|
2149
|
-
test('All input methods should yield consistent results for Raw Key and Entry', () => {
|
|
2150
|
-
const fuzzTree = new BST<number, number>();
|
|
2151
|
-
const randomKeys = generateRandomArray(100, 0, 200);
|
|
2152
|
-
[...new Set(randomKeys)].forEach(k => fuzzTree.add(k));
|
|
2153
|
-
|
|
2154
|
-
const queries = generateRandomArray(50, 0, 200);
|
|
2155
|
-
|
|
2156
|
-
queries.forEach(q => {
|
|
2157
|
-
// 1. Raw Key
|
|
2158
|
-
const resKey = fuzzTree.lowerBound(q);
|
|
2159
|
-
|
|
2160
|
-
// 2. Entry
|
|
2161
|
-
const resEntry = fuzzTree.lowerBound([q, undefined]);
|
|
2162
|
-
|
|
2163
|
-
// Verify consistency between Key and Entry
|
|
2164
|
-
expect(resKey?.key).toBe(resEntry?.key);
|
|
2165
|
-
});
|
|
2166
|
-
});
|
|
2167
|
-
|
|
2168
|
-
test('BSTNode input should work correctly', () => {
|
|
2169
|
-
const fuzzTree = new BST<number, number>();
|
|
2170
|
-
const randomKeys = generateRandomArray(50, 0, 100);
|
|
2171
|
-
const uniqueKeys = [...new Set(randomKeys)];
|
|
2172
|
-
uniqueKeys.forEach(k => fuzzTree.add(k));
|
|
2173
|
-
|
|
2174
|
-
// Test with actual nodes from the tree
|
|
2175
|
-
for (const key of uniqueKeys) {
|
|
2176
|
-
const node = fuzzTree.getNode(key);
|
|
2177
|
-
if (node) {
|
|
2178
|
-
// lowerBound with node should return itself (since node.key >= key)
|
|
2179
|
-
expect(fuzzTree.lowerBound(node)?.key).toBe(key);
|
|
2180
|
-
|
|
2181
|
-
// upperBound with node should return next larger
|
|
2182
|
-
const upperRes = fuzzTree.upperBound(node);
|
|
2183
|
-
if (upperRes) {
|
|
2184
|
-
expect(upperRes.key).toBeGreaterThan(key);
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
});
|
|
2189
|
-
|
|
2190
|
-
test('Predicate input should find matching nodes', () => {
|
|
2191
|
-
const fuzzTree = new BST<number, number>();
|
|
2192
|
-
const randomKeys = generateRandomArray(50, 0, 100);
|
|
2193
|
-
const uniqueKeys = [...new Set(randomKeys)];
|
|
2194
|
-
uniqueKeys.forEach(k => fuzzTree.add(k));
|
|
2195
|
-
|
|
2196
|
-
// Test various predicates
|
|
2197
|
-
const predicates = [(n: any) => n.key > 50, (n: any) => n.key < 30, (n: any) => n.key >= 25 && n.key <= 75];
|
|
2198
|
-
|
|
2199
|
-
predicates.forEach(predicate => {
|
|
2200
|
-
const result = fuzzTree.lowerBound(predicate);
|
|
2201
|
-
if (result) {
|
|
2202
|
-
// Verify the result actually satisfies the predicate
|
|
2203
|
-
expect(predicate(result)).toBe(true);
|
|
2204
|
-
}
|
|
2205
|
-
});
|
|
2206
|
-
});
|
|
2032
|
+
// Use higher to find the first key > 19
|
|
2033
|
+
const nextKey = bst.higher(19);
|
|
2034
|
+
// Use ceiling to find the first key >= 14
|
|
2035
|
+
const firstInRange = bst.ceiling(14);
|
|
2207
2036
|
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
const randomKeys = generateRandomArray(50, 0, 100);
|
|
2211
|
-
[...new Set(randomKeys)].forEach(k => fuzzTree.add(k));
|
|
2212
|
-
|
|
2213
|
-
const queries = generateRandomArray(30, 0, 100);
|
|
2214
|
-
|
|
2215
|
-
queries.forEach(q => {
|
|
2216
|
-
// Test with Key
|
|
2217
|
-
const recKey = fuzzTree.lowerBound(q, 'RECURSIVE');
|
|
2218
|
-
const iterKey = fuzzTree.lowerBound(q, 'ITERATIVE');
|
|
2219
|
-
expect(recKey?.key).toBe(iterKey?.key);
|
|
2220
|
-
|
|
2221
|
-
// Test with Entry
|
|
2222
|
-
const recEntry = fuzzTree.lowerBound([q, undefined], 'RECURSIVE');
|
|
2223
|
-
const iterEntry = fuzzTree.lowerBound([q, undefined], 'ITERATIVE');
|
|
2224
|
-
expect(recEntry?.key).toBe(iterEntry?.key);
|
|
2225
|
-
|
|
2226
|
-
// Test upperBound too
|
|
2227
|
-
const recUpper = fuzzTree.upperBound(q, 'RECURSIVE');
|
|
2228
|
-
const iterUpper = fuzzTree.upperBound(q, 'ITERATIVE');
|
|
2229
|
-
expect(recUpper?.key).toBe(iterUpper?.key);
|
|
2230
|
-
});
|
|
2037
|
+
expect(firstInRange).toBe(15);
|
|
2038
|
+
expect(nextKey).toBe(20);
|
|
2231
2039
|
});
|
|
2232
2040
|
});
|
|
2233
2041
|
});
|
|
@@ -2240,319 +2048,318 @@ describe('BST Range Query Methods', () => {
|
|
|
2240
2048
|
bst = new BST([10, 5, 15, 3, 7, 13, 17, 1, 4, 6, 9, 11, 14, 16, 19, 20]);
|
|
2241
2049
|
});
|
|
2242
2050
|
|
|
2243
|
-
describe('
|
|
2051
|
+
describe('ceiling - finds >= key (minimum value >= target)', () => {
|
|
2244
2052
|
test('should find ceiling when key exists', () => {
|
|
2245
|
-
const result = bst.
|
|
2053
|
+
const result = bst.ceiling(10);
|
|
2246
2054
|
expect(result).toBeDefined();
|
|
2247
|
-
expect(result
|
|
2055
|
+
expect(result).toBe(10);
|
|
2248
2056
|
});
|
|
2249
2057
|
|
|
2250
2058
|
test('should find ceiling when key does not exist but higher value exists', () => {
|
|
2251
|
-
const result = bst.
|
|
2059
|
+
const result = bst.ceiling(8);
|
|
2252
2060
|
expect(result).toBeDefined();
|
|
2253
|
-
expect(result
|
|
2061
|
+
expect(result).toBe(9);
|
|
2254
2062
|
});
|
|
2255
2063
|
|
|
2256
2064
|
test('should return undefined when no ceiling exists (key greater than all)', () => {
|
|
2257
|
-
const result = bst.
|
|
2065
|
+
const result = bst.ceiling(100);
|
|
2258
2066
|
expect(result).toBeUndefined();
|
|
2259
2067
|
});
|
|
2260
2068
|
|
|
2261
2069
|
test('should find minimum element as ceiling for key smaller than all', () => {
|
|
2262
|
-
const result = bst.
|
|
2070
|
+
const result = bst.ceiling(-10);
|
|
2263
2071
|
expect(result).toBeDefined();
|
|
2264
|
-
expect(result
|
|
2072
|
+
expect(result).toBe(1);
|
|
2265
2073
|
});
|
|
2266
2074
|
|
|
2267
2075
|
test('should handle ceiling with node input', () => {
|
|
2268
2076
|
const targetNode = bst.getNode(7);
|
|
2269
2077
|
expect(targetNode).toBeDefined();
|
|
2270
|
-
const result = bst.
|
|
2271
|
-
expect(result
|
|
2078
|
+
const result = bst.ceiling(targetNode!);
|
|
2079
|
+
expect(result).toBe(7);
|
|
2272
2080
|
});
|
|
2273
2081
|
|
|
2274
2082
|
test('should handle ceiling with entry input', () => {
|
|
2275
|
-
const result = bst.
|
|
2083
|
+
const result = bst.ceiling([11, 'test']);
|
|
2276
2084
|
expect(result).toBeDefined();
|
|
2277
|
-
expect(result
|
|
2085
|
+
expect(result).toBe(11);
|
|
2278
2086
|
});
|
|
2279
2087
|
|
|
2280
2088
|
test('should handle null/undefined inputs', () => {
|
|
2281
|
-
expect(bst.
|
|
2282
|
-
expect(bst.
|
|
2089
|
+
expect(bst.ceiling(null)).toBeUndefined();
|
|
2090
|
+
expect(bst.ceiling(undefined)).toBeUndefined();
|
|
2283
2091
|
});
|
|
2284
2092
|
|
|
2285
2093
|
test('should work with ITERATIVE mode', () => {
|
|
2286
|
-
const result = bst.
|
|
2094
|
+
const result = bst.ceiling(12);
|
|
2287
2095
|
expect(result).toBeDefined();
|
|
2288
|
-
expect(result
|
|
2096
|
+
expect(result).toBe(13);
|
|
2289
2097
|
});
|
|
2290
2098
|
|
|
2291
2099
|
test('should work with RECURSIVE mode', () => {
|
|
2292
|
-
const result = bst.
|
|
2100
|
+
const result = bst.ceiling(12);
|
|
2293
2101
|
expect(result).toBeDefined();
|
|
2294
|
-
expect(result
|
|
2102
|
+
expect(result).toBe(13);
|
|
2295
2103
|
});
|
|
2296
2104
|
|
|
2297
2105
|
test('should find exact match as ceiling', () => {
|
|
2298
|
-
const result = bst.
|
|
2106
|
+
const result = bst.ceiling(15);
|
|
2299
2107
|
expect(result).toBeDefined();
|
|
2300
|
-
expect(result
|
|
2108
|
+
expect(result).toBe(15);
|
|
2301
2109
|
});
|
|
2302
2110
|
});
|
|
2303
2111
|
|
|
2304
|
-
describe('
|
|
2112
|
+
describe('higher - finds > key (minimum value > target)', () => {
|
|
2305
2113
|
test('should find higher when key exists (exclude exact match)', () => {
|
|
2306
|
-
const result = bst.
|
|
2114
|
+
const result = bst.higher(10);
|
|
2307
2115
|
expect(result).toBeDefined();
|
|
2308
|
-
expect(result
|
|
2309
|
-
expect(result
|
|
2116
|
+
expect(result).toBe(11);
|
|
2117
|
+
expect(result).not.toBe(10);
|
|
2310
2118
|
});
|
|
2311
2119
|
|
|
2312
2120
|
test('should find higher when key does not exist', () => {
|
|
2313
|
-
const result = bst.
|
|
2121
|
+
const result = bst.higher(8);
|
|
2314
2122
|
expect(result).toBeDefined();
|
|
2315
|
-
expect(result
|
|
2123
|
+
expect(result).toBe(9);
|
|
2316
2124
|
});
|
|
2317
2125
|
|
|
2318
2126
|
test('should return undefined when no higher exists (key >= all)', () => {
|
|
2319
|
-
const result = bst.
|
|
2127
|
+
const result = bst.higher(20);
|
|
2320
2128
|
expect(result).toBeUndefined();
|
|
2321
2129
|
});
|
|
2322
2130
|
|
|
2323
2131
|
test('should find minimum element as higher for key < all', () => {
|
|
2324
|
-
const result = bst.
|
|
2132
|
+
const result = bst.higher(-10);
|
|
2325
2133
|
expect(result).toBeDefined();
|
|
2326
|
-
expect(result
|
|
2134
|
+
expect(result).toBe(1);
|
|
2327
2135
|
});
|
|
2328
2136
|
|
|
2329
2137
|
test('should not return the key itself', () => {
|
|
2330
|
-
const result = bst.
|
|
2331
|
-
expect(result
|
|
2332
|
-
expect(result
|
|
2138
|
+
const result = bst.higher(7);
|
|
2139
|
+
expect(result).not.toBe(7);
|
|
2140
|
+
expect(result).toBe(9);
|
|
2333
2141
|
});
|
|
2334
2142
|
|
|
2335
2143
|
test('should handle higher with node input', () => {
|
|
2336
2144
|
const targetNode = bst.getNode(5);
|
|
2337
2145
|
expect(targetNode).toBeDefined();
|
|
2338
|
-
const result = bst.
|
|
2339
|
-
expect(result
|
|
2340
|
-
expect(result
|
|
2146
|
+
const result = bst.higher(targetNode!);
|
|
2147
|
+
expect(result).toBeGreaterThan(5);
|
|
2148
|
+
expect(result).toBe(6);
|
|
2341
2149
|
});
|
|
2342
2150
|
|
|
2343
2151
|
test('should work with ITERATIVE mode', () => {
|
|
2344
|
-
const result = bst.
|
|
2152
|
+
const result = bst.higher(13);
|
|
2345
2153
|
expect(result).toBeDefined();
|
|
2346
|
-
expect(result
|
|
2154
|
+
expect(result).toBe(14);
|
|
2347
2155
|
});
|
|
2348
2156
|
|
|
2349
2157
|
test('should work with RECURSIVE mode', () => {
|
|
2350
|
-
const result = bst.
|
|
2158
|
+
const result = bst.higher(13);
|
|
2351
2159
|
expect(result).toBeDefined();
|
|
2352
|
-
expect(result
|
|
2160
|
+
expect(result).toBe(14);
|
|
2353
2161
|
});
|
|
2354
2162
|
});
|
|
2355
2163
|
|
|
2356
|
-
describe('
|
|
2164
|
+
describe('floor - finds <= key (maximum value <= target)', () => {
|
|
2357
2165
|
test('should find floor when key exists', () => {
|
|
2358
|
-
const result = bst.
|
|
2166
|
+
const result = bst.floor(10);
|
|
2359
2167
|
expect(result).toBeDefined();
|
|
2360
|
-
expect(result
|
|
2168
|
+
expect(result).toBe(10);
|
|
2361
2169
|
});
|
|
2362
2170
|
|
|
2363
2171
|
test('should find floor when key does not exist but lower value exists', () => {
|
|
2364
|
-
const result = bst.
|
|
2172
|
+
const result = bst.floor(12);
|
|
2365
2173
|
expect(result).toBeDefined();
|
|
2366
|
-
expect(result
|
|
2174
|
+
expect(result).toBe(11);
|
|
2367
2175
|
});
|
|
2368
2176
|
|
|
2369
2177
|
test('should return undefined when no floor exists (key less than all)', () => {
|
|
2370
|
-
const result = bst.
|
|
2178
|
+
const result = bst.floor(-10);
|
|
2371
2179
|
expect(result).toBeUndefined();
|
|
2372
2180
|
});
|
|
2373
2181
|
|
|
2374
2182
|
test('should find maximum element as floor for key greater than all', () => {
|
|
2375
|
-
const result = bst.
|
|
2183
|
+
const result = bst.floor(100);
|
|
2376
2184
|
expect(result).toBeDefined();
|
|
2377
|
-
expect(result
|
|
2185
|
+
expect(result).toBe(20);
|
|
2378
2186
|
});
|
|
2379
2187
|
|
|
2380
2188
|
test('should handle floor with node input', () => {
|
|
2381
2189
|
const targetNode = bst.getNode(13);
|
|
2382
2190
|
expect(targetNode).toBeDefined();
|
|
2383
|
-
const result = bst.
|
|
2384
|
-
expect(result
|
|
2191
|
+
const result = bst.floor(targetNode!);
|
|
2192
|
+
expect(result).toBe(13);
|
|
2385
2193
|
});
|
|
2386
2194
|
|
|
2387
2195
|
test('should handle floor with entry input', () => {
|
|
2388
|
-
const result = bst.
|
|
2196
|
+
const result = bst.floor([16, 'test']);
|
|
2389
2197
|
expect(result).toBeDefined();
|
|
2390
|
-
expect(result
|
|
2198
|
+
expect(result).toBe(16);
|
|
2391
2199
|
});
|
|
2392
2200
|
|
|
2393
2201
|
test('should handle null/undefined inputs', () => {
|
|
2394
|
-
expect(bst.
|
|
2395
|
-
expect(bst.
|
|
2202
|
+
expect(bst.floor(null)).toBeUndefined();
|
|
2203
|
+
expect(bst.floor(undefined)).toBeUndefined();
|
|
2396
2204
|
});
|
|
2397
2205
|
|
|
2398
2206
|
test('should work with ITERATIVE mode', () => {
|
|
2399
|
-
const result = bst.
|
|
2207
|
+
const result = bst.floor(12);
|
|
2400
2208
|
expect(result).toBeDefined();
|
|
2401
|
-
expect(result
|
|
2209
|
+
expect(result).toBe(11);
|
|
2402
2210
|
});
|
|
2403
2211
|
|
|
2404
2212
|
test('should work with RECURSIVE mode', () => {
|
|
2405
|
-
const result = bst.
|
|
2213
|
+
const result = bst.floor(12);
|
|
2406
2214
|
expect(result).toBeDefined();
|
|
2407
|
-
expect(result
|
|
2215
|
+
expect(result).toBe(11);
|
|
2408
2216
|
});
|
|
2409
2217
|
|
|
2410
2218
|
test('should find exact match as floor', () => {
|
|
2411
|
-
const result = bst.
|
|
2219
|
+
const result = bst.floor(15);
|
|
2412
2220
|
expect(result).toBeDefined();
|
|
2413
|
-
expect(result
|
|
2221
|
+
expect(result).toBe(15);
|
|
2414
2222
|
});
|
|
2415
2223
|
|
|
2416
2224
|
test('should correctly find floor between two keys', () => {
|
|
2417
|
-
const result = bst.
|
|
2225
|
+
const result = bst.floor(8);
|
|
2418
2226
|
expect(result).toBeDefined();
|
|
2419
|
-
expect(result
|
|
2420
|
-
expect(result
|
|
2227
|
+
expect(result).toBe(7);
|
|
2228
|
+
expect(result).toBeLessThan(8);
|
|
2421
2229
|
});
|
|
2422
2230
|
});
|
|
2423
2231
|
|
|
2424
|
-
describe('
|
|
2232
|
+
describe('lower - finds < key (maximum value < target)', () => {
|
|
2425
2233
|
test('should find lower when key exists (exclude exact match)', () => {
|
|
2426
|
-
const result = bst.
|
|
2234
|
+
const result = bst.lower(10);
|
|
2427
2235
|
expect(result).toBeDefined();
|
|
2428
|
-
expect(result
|
|
2429
|
-
expect(result
|
|
2236
|
+
expect(result).toBe(9);
|
|
2237
|
+
expect(result).not.toBe(10);
|
|
2430
2238
|
});
|
|
2431
2239
|
|
|
2432
2240
|
test('should find lower when key does not exist', () => {
|
|
2433
|
-
const result = bst.
|
|
2241
|
+
const result = bst.lower(12);
|
|
2434
2242
|
expect(result).toBeDefined();
|
|
2435
|
-
expect(result
|
|
2243
|
+
expect(result).toBe(11);
|
|
2436
2244
|
});
|
|
2437
2245
|
|
|
2438
2246
|
test('should return undefined when no lower exists (key <= all)', () => {
|
|
2439
|
-
const result = bst.
|
|
2247
|
+
const result = bst.lower(1);
|
|
2440
2248
|
expect(result).toBeUndefined();
|
|
2441
2249
|
});
|
|
2442
2250
|
|
|
2443
2251
|
test('should find maximum element as lower for key > all', () => {
|
|
2444
|
-
const result = bst.
|
|
2252
|
+
const result = bst.lower(100);
|
|
2445
2253
|
expect(result).toBeDefined();
|
|
2446
|
-
expect(result
|
|
2254
|
+
expect(result).toBe(20);
|
|
2447
2255
|
});
|
|
2448
2256
|
|
|
2449
2257
|
test('should not return the key itself', () => {
|
|
2450
|
-
const result = bst.
|
|
2451
|
-
expect(result
|
|
2452
|
-
expect(result
|
|
2258
|
+
const result = bst.lower(15);
|
|
2259
|
+
expect(result).not.toBe(15);
|
|
2260
|
+
expect(result).toBe(14);
|
|
2453
2261
|
});
|
|
2454
2262
|
|
|
2455
2263
|
test('should handle lower with node input', () => {
|
|
2456
2264
|
const targetNode = bst.getNode(13);
|
|
2457
2265
|
expect(targetNode).toBeDefined();
|
|
2458
|
-
const result = bst.
|
|
2459
|
-
expect(result
|
|
2460
|
-
expect(result
|
|
2266
|
+
const result = bst.lower(targetNode!);
|
|
2267
|
+
expect(result).toBeLessThan(13);
|
|
2268
|
+
expect(result).toBe(11);
|
|
2461
2269
|
});
|
|
2462
2270
|
|
|
2463
2271
|
test('should handle lower with entry input', () => {
|
|
2464
|
-
const result = bst.
|
|
2272
|
+
const result = bst.lower([17, 'test']);
|
|
2465
2273
|
expect(result).toBeDefined();
|
|
2466
|
-
expect(result
|
|
2274
|
+
expect(result).toBe(16);
|
|
2467
2275
|
});
|
|
2468
2276
|
|
|
2469
2277
|
test('should work with ITERATIVE mode', () => {
|
|
2470
|
-
const result = bst.
|
|
2278
|
+
const result = bst.lower(14);
|
|
2471
2279
|
expect(result).toBeDefined();
|
|
2472
|
-
expect(result
|
|
2280
|
+
expect(result).toBe(13);
|
|
2473
2281
|
});
|
|
2474
2282
|
|
|
2475
2283
|
test('should work with RECURSIVE mode', () => {
|
|
2476
|
-
const result = bst.
|
|
2284
|
+
const result = bst.lower(14);
|
|
2477
2285
|
expect(result).toBeDefined();
|
|
2478
|
-
expect(result
|
|
2286
|
+
expect(result).toBe(13);
|
|
2479
2287
|
});
|
|
2480
2288
|
});
|
|
2481
2289
|
|
|
2482
2290
|
describe('Edge cases and special scenarios', () => {
|
|
2483
2291
|
test('single element tree - ceiling', () => {
|
|
2484
2292
|
const singleBst = new BST([5]);
|
|
2485
|
-
expect(singleBst.
|
|
2486
|
-
expect(singleBst.
|
|
2487
|
-
expect(singleBst.
|
|
2293
|
+
expect(singleBst.ceiling(5)).toBe(5);
|
|
2294
|
+
expect(singleBst.ceiling(3)).toBe(5);
|
|
2295
|
+
expect(singleBst.ceiling(7)).toBeUndefined();
|
|
2488
2296
|
});
|
|
2489
2297
|
|
|
2490
2298
|
test('single element tree - higher', () => {
|
|
2491
2299
|
const singleBst = new BST([5]);
|
|
2492
|
-
expect(singleBst.
|
|
2493
|
-
expect(singleBst.
|
|
2300
|
+
expect(singleBst.higher(5)).toBeUndefined();
|
|
2301
|
+
expect(singleBst.higher(3)).toBe(5);
|
|
2494
2302
|
});
|
|
2495
2303
|
|
|
2496
2304
|
test('single element tree - floor', () => {
|
|
2497
2305
|
const singleBst = new BST([5]);
|
|
2498
|
-
expect(singleBst.
|
|
2499
|
-
expect(singleBst.
|
|
2500
|
-
expect(singleBst.
|
|
2306
|
+
expect(singleBst.floor(5)).toBe(5);
|
|
2307
|
+
expect(singleBst.floor(7)).toBe(5);
|
|
2308
|
+
expect(singleBst.floor(3)).toBeUndefined();
|
|
2501
2309
|
});
|
|
2502
2310
|
|
|
2503
2311
|
test('single element tree - lower', () => {
|
|
2504
2312
|
const singleBst = new BST([5]);
|
|
2505
|
-
expect(singleBst.
|
|
2506
|
-
expect(singleBst.
|
|
2313
|
+
expect(singleBst.lower(5)).toBeUndefined();
|
|
2314
|
+
expect(singleBst.lower(7)).toBe(5);
|
|
2507
2315
|
});
|
|
2508
2316
|
|
|
2509
2317
|
test('empty tree handling', () => {
|
|
2510
2318
|
const emptyBst = new BST<number, string>();
|
|
2511
|
-
expect(emptyBst.
|
|
2512
|
-
expect(emptyBst.
|
|
2513
|
-
expect(emptyBst.
|
|
2514
|
-
expect(emptyBst.
|
|
2319
|
+
expect(emptyBst.ceiling(5)).toBeUndefined();
|
|
2320
|
+
expect(emptyBst.higher(5)).toBeUndefined();
|
|
2321
|
+
expect(emptyBst.floor(5)).toBeUndefined();
|
|
2322
|
+
expect(emptyBst.lower(5)).toBeUndefined();
|
|
2515
2323
|
});
|
|
2516
2324
|
|
|
2517
2325
|
test('ceiling and floor of adjacent keys', () => {
|
|
2518
|
-
const ceiling = bst.
|
|
2519
|
-
const floor = bst.
|
|
2520
|
-
expect(ceiling
|
|
2521
|
-
|
|
2522
|
-
expect(floor?.key).toBe(6);
|
|
2326
|
+
const ceiling = bst.ceiling(5);
|
|
2327
|
+
const floor = bst.floor(6);
|
|
2328
|
+
expect(ceiling).toBe(5);
|
|
2329
|
+
expect(floor).toBe(6);
|
|
2523
2330
|
});
|
|
2524
2331
|
|
|
2525
2332
|
test('higher and lower of adjacent keys', () => {
|
|
2526
|
-
const higher = bst.
|
|
2527
|
-
const lower = bst.
|
|
2528
|
-
expect(higher
|
|
2529
|
-
expect(lower
|
|
2333
|
+
const higher = bst.higher(5);
|
|
2334
|
+
const lower = bst.lower(6);
|
|
2335
|
+
expect(higher).toBe(6);
|
|
2336
|
+
expect(lower).toBe(5);
|
|
2530
2337
|
});
|
|
2531
2338
|
});
|
|
2532
2339
|
|
|
2533
2340
|
describe('Predicate-based search', () => {
|
|
2534
2341
|
test('ceiling with predicate function', () => {
|
|
2535
|
-
const result = bst.
|
|
2342
|
+
const result = bst.ceiling((node: BSTNode<number, string>) => node.key >= 10);
|
|
2536
2343
|
expect(result).toBeDefined();
|
|
2537
|
-
expect(result
|
|
2344
|
+
expect(result).toBeGreaterThanOrEqual(10);
|
|
2538
2345
|
});
|
|
2539
2346
|
|
|
2540
2347
|
test('floor with predicate function', () => {
|
|
2541
|
-
const result = bst.
|
|
2348
|
+
const result = bst.floor((node: BSTNode<number, string>) => node.key <= 15);
|
|
2542
2349
|
expect(result).toBeDefined();
|
|
2543
|
-
expect(result
|
|
2350
|
+
expect(result).toBeLessThanOrEqual(15);
|
|
2544
2351
|
});
|
|
2545
2352
|
|
|
2546
2353
|
test('higher with predicate function', () => {
|
|
2547
|
-
const result = bst.
|
|
2354
|
+
const result = bst.higher((node: BSTNode<number, string>) => node.key > 10);
|
|
2548
2355
|
expect(result).toBeDefined();
|
|
2549
|
-
expect(result
|
|
2356
|
+
expect(result).toBeGreaterThan(10);
|
|
2550
2357
|
});
|
|
2551
2358
|
|
|
2552
2359
|
test('lower with predicate function', () => {
|
|
2553
|
-
const result = bst.
|
|
2360
|
+
const result = bst.lower((node: BSTNode<number, string>) => node.key < 15);
|
|
2554
2361
|
expect(result).toBeDefined();
|
|
2555
|
-
expect(result
|
|
2362
|
+
expect(result).toBeLessThan(15);
|
|
2556
2363
|
});
|
|
2557
2364
|
});
|
|
2558
2365
|
|
|
@@ -2564,9 +2371,9 @@ describe('BST Range Query Methods', () => {
|
|
|
2564
2371
|
|
|
2565
2372
|
// In reverse order tree: keys are stored in descending order
|
|
2566
2373
|
// ceiling (>=) should still work correctly
|
|
2567
|
-
const ceiling = reverseBst.
|
|
2374
|
+
const ceiling = reverseBst.ceiling(10);
|
|
2568
2375
|
expect(ceiling).toBeDefined();
|
|
2569
|
-
expect(ceiling
|
|
2376
|
+
expect(ceiling).toBeLessThanOrEqual(10);
|
|
2570
2377
|
});
|
|
2571
2378
|
|
|
2572
2379
|
test('should work with string comparator', () => {
|
|
@@ -2583,36 +2390,36 @@ describe('BST Range Query Methods', () => {
|
|
|
2583
2390
|
}
|
|
2584
2391
|
);
|
|
2585
2392
|
|
|
2586
|
-
const ceiling = stringBst.
|
|
2393
|
+
const ceiling = stringBst.ceiling({ name: 'Bob', id: 0 });
|
|
2587
2394
|
expect(ceiling).toBeDefined();
|
|
2588
|
-
expect(ceiling?.
|
|
2395
|
+
expect(ceiling?.name).toBe('Bob');
|
|
2589
2396
|
});
|
|
2590
2397
|
});
|
|
2591
2398
|
|
|
2592
2399
|
describe('Performance and correctness validation', () => {
|
|
2593
|
-
test('all range methods return
|
|
2594
|
-
const ceiling = bst.
|
|
2595
|
-
const higher = bst.
|
|
2596
|
-
const floor = bst.
|
|
2597
|
-
const lower = bst.
|
|
2400
|
+
test('all range methods return keys in order', () => {
|
|
2401
|
+
const ceiling = bst.ceiling(10);
|
|
2402
|
+
const higher = bst.higher(10);
|
|
2403
|
+
const floor = bst.floor(10);
|
|
2404
|
+
const lower = bst.lower(10);
|
|
2598
2405
|
|
|
2599
|
-
expect(floor
|
|
2600
|
-
expect(ceiling
|
|
2601
|
-
expect(higher
|
|
2602
|
-
expect(lower
|
|
2406
|
+
expect(floor).toBeLessThanOrEqual(10);
|
|
2407
|
+
expect(ceiling).toBeGreaterThanOrEqual(10);
|
|
2408
|
+
expect(higher).toBeGreaterThan(10);
|
|
2409
|
+
expect(lower).toBeLessThan(10);
|
|
2603
2410
|
});
|
|
2604
2411
|
|
|
2605
2412
|
test('range query iteration with ceiling/higher', () => {
|
|
2606
2413
|
const results: number[] = [];
|
|
2607
|
-
let
|
|
2414
|
+
let key = bst.ceiling(5);
|
|
2608
2415
|
let count = 0;
|
|
2609
|
-
while (
|
|
2610
|
-
results.push(
|
|
2611
|
-
|
|
2416
|
+
while (key && key <= 15 && count < 20) {
|
|
2417
|
+
results.push(key);
|
|
2418
|
+
key = bst.higher(key);
|
|
2612
2419
|
count++;
|
|
2613
2420
|
}
|
|
2614
2421
|
|
|
2615
|
-
// Should iterate through
|
|
2422
|
+
// Should iterate through keys 5, 6, 7, ..., 15
|
|
2616
2423
|
expect(results.length).toBeGreaterThan(0);
|
|
2617
2424
|
expect(results[0]).toBeGreaterThanOrEqual(5);
|
|
2618
2425
|
expect(results[results.length - 1]).toBeLessThanOrEqual(15);
|
|
@@ -2624,15 +2431,15 @@ describe('BST Range Query Methods', () => {
|
|
|
2624
2431
|
|
|
2625
2432
|
test('range query iteration with floor/lower', () => {
|
|
2626
2433
|
const results: number[] = [];
|
|
2627
|
-
let
|
|
2434
|
+
let key = bst.floor(15);
|
|
2628
2435
|
let count = 0;
|
|
2629
|
-
while (
|
|
2630
|
-
results.push(
|
|
2631
|
-
|
|
2436
|
+
while (key && key >= 5 && count < 20) {
|
|
2437
|
+
results.push(key);
|
|
2438
|
+
key = bst.lower(key);
|
|
2632
2439
|
count++;
|
|
2633
2440
|
}
|
|
2634
2441
|
|
|
2635
|
-
// Should iterate through
|
|
2442
|
+
// Should iterate through keys 15, 14, 13, ..., 5
|
|
2636
2443
|
expect(results.length).toBeGreaterThan(0);
|
|
2637
2444
|
expect(results[0]).toBeLessThanOrEqual(15);
|
|
2638
2445
|
expect(results[results.length - 1]).toBeGreaterThanOrEqual(5);
|
|
@@ -2645,47 +2452,853 @@ describe('BST Range Query Methods', () => {
|
|
|
2645
2452
|
|
|
2646
2453
|
describe('Boundary value testing', () => {
|
|
2647
2454
|
test('boundary: ceiling at min value', () => {
|
|
2648
|
-
const result = bst.
|
|
2649
|
-
expect(result
|
|
2455
|
+
const result = bst.ceiling(1);
|
|
2456
|
+
expect(result).toBe(1);
|
|
2650
2457
|
});
|
|
2651
2458
|
|
|
2652
2459
|
test('boundary: floor at max value', () => {
|
|
2653
|
-
const result = bst.
|
|
2654
|
-
expect(result
|
|
2460
|
+
const result = bst.floor(20);
|
|
2461
|
+
expect(result).toBe(20);
|
|
2655
2462
|
});
|
|
2656
2463
|
|
|
2657
2464
|
test('boundary: higher at second-last value', () => {
|
|
2658
|
-
const result = bst.
|
|
2659
|
-
expect(result
|
|
2465
|
+
const result = bst.higher(19);
|
|
2466
|
+
expect(result).toBe(20);
|
|
2660
2467
|
});
|
|
2661
2468
|
|
|
2662
2469
|
test('boundary: lower at second value', () => {
|
|
2663
|
-
const result = bst.
|
|
2664
|
-
expect(result
|
|
2470
|
+
const result = bst.lower(3);
|
|
2471
|
+
expect(result).toBe(1);
|
|
2665
2472
|
});
|
|
2666
2473
|
|
|
2667
2474
|
test('boundary: ceiling slightly below min', () => {
|
|
2668
|
-
const result = bst.
|
|
2669
|
-
expect(result
|
|
2475
|
+
const result = bst.ceiling(0);
|
|
2476
|
+
expect(result).toBe(1);
|
|
2670
2477
|
});
|
|
2671
2478
|
|
|
2672
2479
|
test('boundary: floor slightly above max', () => {
|
|
2673
|
-
const result = bst.
|
|
2674
|
-
expect(result
|
|
2480
|
+
const result = bst.floor(21);
|
|
2481
|
+
expect(result).toBe(20);
|
|
2675
2482
|
});
|
|
2676
2483
|
|
|
2677
2484
|
test('boundary: higher at max (should be undefined)', () => {
|
|
2678
|
-
const result = bst.
|
|
2485
|
+
const result = bst.higher(20);
|
|
2679
2486
|
expect(result).toBeUndefined();
|
|
2680
2487
|
});
|
|
2681
2488
|
|
|
2682
2489
|
test('boundary: lower at min (should be undefined)', () => {
|
|
2683
|
-
const result = bst.
|
|
2490
|
+
const result = bst.lower(1);
|
|
2684
2491
|
expect(result).toBeUndefined();
|
|
2685
2492
|
});
|
|
2686
2493
|
});
|
|
2687
2494
|
});
|
|
2688
2495
|
|
|
2496
|
+
describe('BST Comparator Tests', () => {
|
|
2497
|
+
describe('Default Comparator with Primitive Types', () => {
|
|
2498
|
+
let bst: BST<number>;
|
|
2499
|
+
|
|
2500
|
+
beforeEach(() => {
|
|
2501
|
+
bst = new BST<number>();
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
it('should compare two numbers correctly - a > b', () => {
|
|
2505
|
+
const result = bst['_compare'](5, 3);
|
|
2506
|
+
expect(result).toBe(1);
|
|
2507
|
+
});
|
|
2508
|
+
|
|
2509
|
+
it('should compare two numbers correctly - a < b', () => {
|
|
2510
|
+
const result = bst['_compare'](2, 8);
|
|
2511
|
+
expect(result).toBe(-1);
|
|
2512
|
+
});
|
|
2513
|
+
|
|
2514
|
+
it('should compare two numbers correctly - a === b', () => {
|
|
2515
|
+
const result = bst['_compare'](5, 5);
|
|
2516
|
+
expect(result).toBe(0);
|
|
2517
|
+
});
|
|
2518
|
+
|
|
2519
|
+
it('should compare negative numbers correctly', () => {
|
|
2520
|
+
const result1 = bst['_compare'](-5, -3);
|
|
2521
|
+
const result2 = bst['_compare'](-10, 0);
|
|
2522
|
+
expect(result1).toBe(-1);
|
|
2523
|
+
expect(result2).toBe(-1);
|
|
2524
|
+
});
|
|
2525
|
+
|
|
2526
|
+
it('should compare zero correctly', () => {
|
|
2527
|
+
const result1 = bst['_compare'](0, 5);
|
|
2528
|
+
const result2 = bst['_compare'](0, 0);
|
|
2529
|
+
const result3 = bst['_compare'](-5, 0);
|
|
2530
|
+
expect(result1).toBe(-1);
|
|
2531
|
+
expect(result2).toBe(0);
|
|
2532
|
+
expect(result3).toBe(-1);
|
|
2533
|
+
});
|
|
2534
|
+
|
|
2535
|
+
it('should compare decimal numbers correctly', () => {
|
|
2536
|
+
const result1 = bst['_compare'](3.14, 3.15);
|
|
2537
|
+
const result2 = bst['_compare'](2.5, 2.5);
|
|
2538
|
+
expect(result1).toBe(-1);
|
|
2539
|
+
expect(result2).toBe(0);
|
|
2540
|
+
});
|
|
2541
|
+
|
|
2542
|
+
it('should compare very large numbers correctly', () => {
|
|
2543
|
+
const result1 = bst['_compare'](Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER - 1);
|
|
2544
|
+
const result2 = bst['_compare'](1e10, 1e9);
|
|
2545
|
+
expect(result1).toBe(1);
|
|
2546
|
+
expect(result2).toBe(1);
|
|
2547
|
+
});
|
|
2548
|
+
});
|
|
2549
|
+
|
|
2550
|
+
describe('Default Comparator with String Types', () => {
|
|
2551
|
+
let bst: BST<string>;
|
|
2552
|
+
|
|
2553
|
+
beforeEach(() => {
|
|
2554
|
+
bst = new BST<string>();
|
|
2555
|
+
});
|
|
2556
|
+
|
|
2557
|
+
it('should compare strings alphabetically - a > b', () => {
|
|
2558
|
+
const result = bst['_compare']('zebra', 'apple');
|
|
2559
|
+
expect(result).toBe(1);
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2562
|
+
it('should compare strings alphabetically - a < b', () => {
|
|
2563
|
+
const result = bst['_compare']('apple', 'zebra');
|
|
2564
|
+
expect(result).toBe(-1);
|
|
2565
|
+
});
|
|
2566
|
+
|
|
2567
|
+
it('should compare identical strings', () => {
|
|
2568
|
+
const result = bst['_compare']('hello', 'hello');
|
|
2569
|
+
expect(result).toBe(0);
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2572
|
+
it('should compare string prefixes correctly', () => {
|
|
2573
|
+
const result1 = bst['_compare']('app', 'apple');
|
|
2574
|
+
const result2 = bst['_compare']('apple', 'app');
|
|
2575
|
+
expect(result1).toBe(-1);
|
|
2576
|
+
expect(result2).toBe(1);
|
|
2577
|
+
});
|
|
2578
|
+
|
|
2579
|
+
it('should compare case-sensitive strings', () => {
|
|
2580
|
+
const result1 = bst['_compare']('Apple', 'apple');
|
|
2581
|
+
const result2 = bst['_compare']('apple', 'Apple');
|
|
2582
|
+
expect(result1).toBe(-1);
|
|
2583
|
+
expect(result2).toBe(1);
|
|
2584
|
+
});
|
|
2585
|
+
|
|
2586
|
+
it('should compare empty strings', () => {
|
|
2587
|
+
const result1 = bst['_compare']('', '');
|
|
2588
|
+
const result2 = bst['_compare']('', 'a');
|
|
2589
|
+
const result3 = bst['_compare']('a', '');
|
|
2590
|
+
expect(result1).toBe(0);
|
|
2591
|
+
expect(result2).toBe(-1);
|
|
2592
|
+
expect(result3).toBe(1);
|
|
2593
|
+
});
|
|
2594
|
+
|
|
2595
|
+
it('should compare special characters in strings', () => {
|
|
2596
|
+
const result1 = bst['_compare']('!', 'a');
|
|
2597
|
+
const result2 = bst['_compare']('a', '1');
|
|
2598
|
+
expect(result1).toBe(-1);
|
|
2599
|
+
expect(result2).toBe(1);
|
|
2600
|
+
});
|
|
2601
|
+
});
|
|
2602
|
+
|
|
2603
|
+
describe('Custom Comparator', () => {
|
|
2604
|
+
interface Person {
|
|
2605
|
+
name: string;
|
|
2606
|
+
age: number;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
it('should accept custom comparator for objects - compare by age', () => {
|
|
2610
|
+
const comparator = (a: Person, b: Person): number => {
|
|
2611
|
+
if (a.age > b.age) return 1;
|
|
2612
|
+
if (a.age < b.age) return -1;
|
|
2613
|
+
return 0;
|
|
2614
|
+
};
|
|
2615
|
+
|
|
2616
|
+
const bst = new BST<Person>([], { comparator });
|
|
2617
|
+
|
|
2618
|
+
const p1 = { name: 'Alice', age: 30 };
|
|
2619
|
+
const p2 = { name: 'Bob', age: 25 };
|
|
2620
|
+
const p3 = { name: 'Charlie', age: 30 };
|
|
2621
|
+
|
|
2622
|
+
expect(bst['_compare'](p1, p2)).toBe(1);
|
|
2623
|
+
expect(bst['_compare'](p2, p1)).toBe(-1);
|
|
2624
|
+
expect(bst['_compare'](p1, p3)).toBe(0);
|
|
2625
|
+
});
|
|
2626
|
+
|
|
2627
|
+
it('should accept custom comparator - compare by name length', () => {
|
|
2628
|
+
const comparator = (a: string, b: string): number => {
|
|
2629
|
+
if (a.length > b.length) return 1;
|
|
2630
|
+
if (a.length < b.length) return -1;
|
|
2631
|
+
return 0;
|
|
2632
|
+
};
|
|
2633
|
+
|
|
2634
|
+
const bst = new BST<string>([], { comparator });
|
|
2635
|
+
|
|
2636
|
+
expect(bst['_compare']('hello', 'hi')).toBe(1);
|
|
2637
|
+
expect(bst['_compare']('hi', 'hello')).toBe(-1);
|
|
2638
|
+
expect(bst['_compare']('abc', 'def')).toBe(0);
|
|
2639
|
+
});
|
|
2640
|
+
|
|
2641
|
+
it('should accept custom comparator - reverse order', () => {
|
|
2642
|
+
const comparator = (a: number, b: number): number => {
|
|
2643
|
+
if (a < b) return 1;
|
|
2644
|
+
if (a > b) return -1;
|
|
2645
|
+
return 0;
|
|
2646
|
+
};
|
|
2647
|
+
|
|
2648
|
+
const bst = new BST<number>([], { comparator });
|
|
2649
|
+
|
|
2650
|
+
expect(bst['_compare'](5, 3)).toBe(-1);
|
|
2651
|
+
expect(bst['_compare'](2, 8)).toBe(1);
|
|
2652
|
+
expect(bst['_compare'](5, 5)).toBe(0);
|
|
2653
|
+
});
|
|
2654
|
+
|
|
2655
|
+
it('should throw error when comparing object types without custom comparator', () => {
|
|
2656
|
+
const bst = new BST<{ value: number }>();
|
|
2657
|
+
|
|
2658
|
+
expect(() => {
|
|
2659
|
+
bst['_compare']({ value: 1 }, { value: 2 });
|
|
2660
|
+
}).toThrow("When comparing object type keys, a custom comparator must be provided in the constructor's options!");
|
|
2661
|
+
});
|
|
2662
|
+
});
|
|
2663
|
+
|
|
2664
|
+
describe('Comparator Usage in BST Operations', () => {
|
|
2665
|
+
let bst: BST<number>;
|
|
2666
|
+
|
|
2667
|
+
beforeEach(() => {
|
|
2668
|
+
bst = new BST<number>();
|
|
2669
|
+
});
|
|
2670
|
+
|
|
2671
|
+
it('should correctly insert elements based on comparator', () => {
|
|
2672
|
+
bst.add(10);
|
|
2673
|
+
bst.add(5);
|
|
2674
|
+
bst.add(15);
|
|
2675
|
+
bst.add(3);
|
|
2676
|
+
bst.add(7);
|
|
2677
|
+
|
|
2678
|
+
expect(bst.has(10)).toBe(true);
|
|
2679
|
+
expect(bst.has(5)).toBe(true);
|
|
2680
|
+
expect(bst.has(15)).toBe(true);
|
|
2681
|
+
expect(bst.has(3)).toBe(true);
|
|
2682
|
+
expect(bst.has(7)).toBe(true);
|
|
2683
|
+
expect(bst.size).toBe(5);
|
|
2684
|
+
});
|
|
2685
|
+
|
|
2686
|
+
it('should maintain BST property with in-order traversal', () => {
|
|
2687
|
+
bst.addMany([11, 3, 15, 1, 8, 13, 16, 2, 6, 9, 12, 14, 4, 7, 10, 5]);
|
|
2688
|
+
|
|
2689
|
+
const inOrder: (number | undefined)[] = bst.dfs(node => node?.key);
|
|
2690
|
+
|
|
2691
|
+
for (let i = 1; i < inOrder.length; i++) {
|
|
2692
|
+
expect(inOrder[i]! >= inOrder[i - 1]!).toBe(true);
|
|
2693
|
+
}
|
|
2694
|
+
});
|
|
2695
|
+
|
|
2696
|
+
it('should correctly find min and max using comparator', () => {
|
|
2697
|
+
bst.addMany([15, 10, 20, 8, 12, 18, 25]);
|
|
2698
|
+
|
|
2699
|
+
const arr = Array.from(bst.keys());
|
|
2700
|
+
const min = Math.min(...arr);
|
|
2701
|
+
const max = Math.max(...arr);
|
|
2702
|
+
|
|
2703
|
+
expect(bst.has(min)).toBe(true);
|
|
2704
|
+
expect(bst.has(max)).toBe(true);
|
|
2705
|
+
});
|
|
2706
|
+
|
|
2707
|
+
it('should correctly delete elements maintaining BST property', () => {
|
|
2708
|
+
bst.addMany([10, 5, 15, 3, 7, 12, 18]);
|
|
2709
|
+
bst.delete(10);
|
|
2710
|
+
|
|
2711
|
+
expect(bst.has(10)).toBe(false);
|
|
2712
|
+
const inOrder: (number | undefined)[] = bst.dfs(node => node?.key);
|
|
2713
|
+
|
|
2714
|
+
for (let i = 1; i < inOrder.length; i++) {
|
|
2715
|
+
expect(inOrder[i]! >= inOrder[i - 1]!).toBe(true);
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
|
|
2719
|
+
it('should correctly search using comparator', () => {
|
|
2720
|
+
bst.addMany([20, 10, 30, 5, 15, 25, 35]);
|
|
2721
|
+
|
|
2722
|
+
const result = bst.search(15);
|
|
2723
|
+
expect(result.length).toBeGreaterThan(0);
|
|
2724
|
+
expect(result[0]).toBe(15);
|
|
2725
|
+
});
|
|
2726
|
+
});
|
|
2727
|
+
|
|
2728
|
+
describe('Comparator Edge Cases', () => {
|
|
2729
|
+
describe('with numbers', () => {
|
|
2730
|
+
let bst: BST<number>;
|
|
2731
|
+
|
|
2732
|
+
beforeEach(() => {
|
|
2733
|
+
bst = new BST<number>();
|
|
2734
|
+
});
|
|
2735
|
+
|
|
2736
|
+
it('should handle Infinity correctly', () => {
|
|
2737
|
+
const result1 = bst['_compare'](Infinity, 100);
|
|
2738
|
+
const result2 = bst['_compare'](100, Infinity);
|
|
2739
|
+
expect(result1).toBe(1);
|
|
2740
|
+
expect(result2).toBe(-1);
|
|
2741
|
+
});
|
|
2742
|
+
|
|
2743
|
+
it('should handle -Infinity correctly', () => {
|
|
2744
|
+
const result1 = bst['_compare'](-Infinity, -100);
|
|
2745
|
+
const result2 = bst['_compare'](-100, -Infinity);
|
|
2746
|
+
expect(result1).toBe(-1);
|
|
2747
|
+
expect(result2).toBe(1);
|
|
2748
|
+
});
|
|
2749
|
+
|
|
2750
|
+
it('should handle NaN correctly', () => {
|
|
2751
|
+
const result1 = bst['_compare'](NaN, 5);
|
|
2752
|
+
const result2 = bst['_compare'](5, NaN);
|
|
2753
|
+
expect(result1).toBe(0);
|
|
2754
|
+
expect(result2).toBe(0);
|
|
2755
|
+
});
|
|
2756
|
+
});
|
|
2757
|
+
|
|
2758
|
+
describe('with strings', () => {
|
|
2759
|
+
let bst: BST<string>;
|
|
2760
|
+
|
|
2761
|
+
beforeEach(() => {
|
|
2762
|
+
bst = new BST<string>();
|
|
2763
|
+
});
|
|
2764
|
+
|
|
2765
|
+
it('should handle Unicode characters', () => {
|
|
2766
|
+
const result1 = bst['_compare']('😀', 'abc');
|
|
2767
|
+
const result2 = bst['_compare']('中文', 'English');
|
|
2768
|
+
expect(typeof result1).toBe('number');
|
|
2769
|
+
expect(typeof result2).toBe('number');
|
|
2770
|
+
});
|
|
2771
|
+
|
|
2772
|
+
it('should handle whitespace strings', () => {
|
|
2773
|
+
const result1 = bst['_compare'](' ', ' ');
|
|
2774
|
+
const result2 = bst['_compare']('\n', '\t');
|
|
2775
|
+
expect(typeof result1).toBe('number');
|
|
2776
|
+
expect(typeof result2).toBe('number');
|
|
2777
|
+
});
|
|
2778
|
+
|
|
2779
|
+
it('should maintain consistency with multiple comparisons', () => {
|
|
2780
|
+
const a = 'apple';
|
|
2781
|
+
const b = 'banana';
|
|
2782
|
+
const c = 'cherry';
|
|
2783
|
+
|
|
2784
|
+
const ab = bst['_compare'](a, b);
|
|
2785
|
+
const bc = bst['_compare'](b, c);
|
|
2786
|
+
const ac = bst['_compare'](a, c);
|
|
2787
|
+
|
|
2788
|
+
if (ab < 0 && bc < 0) {
|
|
2789
|
+
expect(ac).toBeLessThan(0);
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
});
|
|
2793
|
+
});
|
|
2794
|
+
|
|
2795
|
+
describe('Comparator with BST Bound Operations', () => {
|
|
2796
|
+
let bst: BST<number>;
|
|
2797
|
+
|
|
2798
|
+
beforeEach(() => {
|
|
2799
|
+
bst = new BST<number>();
|
|
2800
|
+
bst.addMany([10, 5, 15, 3, 7, 12, 18, 1, 4, 6, 8, 11, 13, 16, 20]);
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
it('should find ceiling using comparator', () => {
|
|
2804
|
+
const result = bst.ceiling(7);
|
|
2805
|
+
expect(result).toBe(7);
|
|
2806
|
+
|
|
2807
|
+
const result2 = bst.ceiling(7.5);
|
|
2808
|
+
expect(result2).toBeGreaterThanOrEqual(7.5);
|
|
2809
|
+
});
|
|
2810
|
+
|
|
2811
|
+
it('should find floor using comparator', () => {
|
|
2812
|
+
const result = bst.floor(7);
|
|
2813
|
+
expect(result).toBe(7);
|
|
2814
|
+
|
|
2815
|
+
const result2 = bst.floor(7.5);
|
|
2816
|
+
expect(result2!).toBeLessThanOrEqual(7.5);
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
it('should find higher using comparator', () => {
|
|
2820
|
+
const result = bst.higher(7);
|
|
2821
|
+
expect(result).toBeGreaterThan(7);
|
|
2822
|
+
});
|
|
2823
|
+
|
|
2824
|
+
it('should find lower using comparator', () => {
|
|
2825
|
+
const result = bst.lower(7);
|
|
2826
|
+
expect(result).toBeLessThan(7);
|
|
2827
|
+
});
|
|
2828
|
+
});
|
|
2829
|
+
|
|
2830
|
+
describe('Comparator Consistency', () => {
|
|
2831
|
+
let bst: BST<number>;
|
|
2832
|
+
|
|
2833
|
+
beforeEach(() => {
|
|
2834
|
+
bst = new BST<number>();
|
|
2835
|
+
});
|
|
2836
|
+
|
|
2837
|
+
it('should satisfy reflexivity: a compared with itself returns 0', () => {
|
|
2838
|
+
const values = [0, 1, -1, 100, -100, 0.5];
|
|
2839
|
+
|
|
2840
|
+
for (const val of values) {
|
|
2841
|
+
expect(bst['_compare'](val, val)).toBe(0);
|
|
2842
|
+
}
|
|
2843
|
+
});
|
|
2844
|
+
|
|
2845
|
+
it('should satisfy antisymmetry: if a > b then b < a', () => {
|
|
2846
|
+
const pairs = [
|
|
2847
|
+
[5, 3],
|
|
2848
|
+
[10, 2],
|
|
2849
|
+
[100, 1]
|
|
2850
|
+
];
|
|
2851
|
+
|
|
2852
|
+
for (const [a, b] of pairs) {
|
|
2853
|
+
const cmp1 = bst['_compare'](a, b);
|
|
2854
|
+
const cmp2 = bst['_compare'](b, a);
|
|
2855
|
+
expect(Math.sign(cmp1)).toBe(-Math.sign(cmp2));
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2858
|
+
|
|
2859
|
+
it('should satisfy transitivity: if a < b and b < c then a < c', () => {
|
|
2860
|
+
const a = 1;
|
|
2861
|
+
const b = 5;
|
|
2862
|
+
const c = 10;
|
|
2863
|
+
|
|
2864
|
+
const cmpAB = bst['_compare'](a, b);
|
|
2865
|
+
const cmpBC = bst['_compare'](b, c);
|
|
2866
|
+
const cmpAC = bst['_compare'](a, c);
|
|
2867
|
+
|
|
2868
|
+
if (cmpAB < 0 && cmpBC < 0) {
|
|
2869
|
+
expect(cmpAC).toBeLessThan(0);
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2872
|
+
});
|
|
2873
|
+
|
|
2874
|
+
describe('Custom Comparator with BST Operations', () => {
|
|
2875
|
+
interface Student {
|
|
2876
|
+
name: string;
|
|
2877
|
+
grade: number;
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
it('should work with custom comparator for complex operations', () => {
|
|
2881
|
+
const comparator = (a: Student, b: Student): number => {
|
|
2882
|
+
if (a.grade !== b.grade) {
|
|
2883
|
+
return a.grade > b.grade ? 1 : -1;
|
|
2884
|
+
}
|
|
2885
|
+
return a.name.localeCompare(b.name);
|
|
2886
|
+
};
|
|
2887
|
+
|
|
2888
|
+
const bst = new BST<Student>([], { comparator });
|
|
2889
|
+
|
|
2890
|
+
const students: Student[] = [
|
|
2891
|
+
{ name: 'Alice', grade: 85 },
|
|
2892
|
+
{ name: 'Bob', grade: 90 },
|
|
2893
|
+
{ name: 'Charlie', grade: 85 },
|
|
2894
|
+
{ name: 'David', grade: 95 }
|
|
2895
|
+
];
|
|
2896
|
+
|
|
2897
|
+
students.forEach(s => bst.add(s));
|
|
2898
|
+
|
|
2899
|
+
expect(bst.size).toBe(4);
|
|
2900
|
+
expect(bst.has(students[0])).toBe(true);
|
|
2901
|
+
});
|
|
2902
|
+
|
|
2903
|
+
it('should handle custom comparator with add and search', () => {
|
|
2904
|
+
const comparator = (a: number[], b: number[]): number => {
|
|
2905
|
+
const sumA = a.reduce((acc, val) => acc + val, 0);
|
|
2906
|
+
const sumB = b.reduce((acc, val) => acc + val, 0);
|
|
2907
|
+
if (sumA > sumB) return 1;
|
|
2908
|
+
if (sumA < sumB) return -1;
|
|
2909
|
+
return 0;
|
|
2910
|
+
};
|
|
2911
|
+
|
|
2912
|
+
const bst = new BST<number[]>([], { comparator });
|
|
2913
|
+
|
|
2914
|
+
bst.add([1, 2, 3]);
|
|
2915
|
+
bst.add([2, 2, 2]);
|
|
2916
|
+
bst.add([1, 1, 1]);
|
|
2917
|
+
|
|
2918
|
+
expect(bst.size).toBeGreaterThan(0);
|
|
2919
|
+
});
|
|
2920
|
+
});
|
|
2921
|
+
|
|
2922
|
+
describe('Composite Key Comparator Tests', () => {
|
|
2923
|
+
interface CompositeKey {
|
|
2924
|
+
departmentId: number;
|
|
2925
|
+
employeeId: number;
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
it('should compare composite keys by multiple fields - primary then secondary', () => {
|
|
2929
|
+
const comparator = (a: CompositeKey, b: CompositeKey): number => {
|
|
2930
|
+
if (a.departmentId !== b.departmentId) {
|
|
2931
|
+
return a.departmentId > b.departmentId ? 1 : -1;
|
|
2932
|
+
}
|
|
2933
|
+
if (a.employeeId !== b.employeeId) {
|
|
2934
|
+
return a.employeeId > b.employeeId ? 1 : -1;
|
|
2935
|
+
}
|
|
2936
|
+
return 0;
|
|
2937
|
+
};
|
|
2938
|
+
|
|
2939
|
+
const bst = new BST<CompositeKey>([], { comparator });
|
|
2940
|
+
|
|
2941
|
+
const key1 = { departmentId: 1, employeeId: 101 };
|
|
2942
|
+
const key2 = { departmentId: 1, employeeId: 102 };
|
|
2943
|
+
const key3 = { departmentId: 2, employeeId: 101 };
|
|
2944
|
+
|
|
2945
|
+
expect(bst['_compare'](key1, key2)).toBe(-1);
|
|
2946
|
+
expect(bst['_compare'](key2, key3)).toBe(-1);
|
|
2947
|
+
expect(bst['_compare'](key1, key1)).toBe(0);
|
|
2948
|
+
});
|
|
2949
|
+
|
|
2950
|
+
it('should maintain BST property with composite keys', () => {
|
|
2951
|
+
const comparator = (a: CompositeKey, b: CompositeKey): number => {
|
|
2952
|
+
if (a.departmentId !== b.departmentId) {
|
|
2953
|
+
return a.departmentId > b.departmentId ? 1 : -1;
|
|
2954
|
+
}
|
|
2955
|
+
return a.employeeId > b.employeeId ? 1 : a.employeeId < b.employeeId ? -1 : 0;
|
|
2956
|
+
};
|
|
2957
|
+
|
|
2958
|
+
const bst = new BST<CompositeKey>([], { comparator });
|
|
2959
|
+
|
|
2960
|
+
const keys = [
|
|
2961
|
+
{ departmentId: 2, employeeId: 105 },
|
|
2962
|
+
{ departmentId: 1, employeeId: 101 },
|
|
2963
|
+
{ departmentId: 1, employeeId: 103 },
|
|
2964
|
+
{ departmentId: 2, employeeId: 102 },
|
|
2965
|
+
{ departmentId: 1, employeeId: 102 }
|
|
2966
|
+
];
|
|
2967
|
+
|
|
2968
|
+
keys.forEach(k => bst.add(k));
|
|
2969
|
+
|
|
2970
|
+
expect(bst.size).toBe(5);
|
|
2971
|
+
const inOrder = bst.dfs(node => node?.key);
|
|
2972
|
+
|
|
2973
|
+
for (let i = 1; i < inOrder.length; i++) {
|
|
2974
|
+
const cmp = comparator(inOrder[i - 1]!, inOrder[i]!);
|
|
2975
|
+
expect(cmp).toBeLessThanOrEqual(0);
|
|
2976
|
+
}
|
|
2977
|
+
});
|
|
2978
|
+
|
|
2979
|
+
it('should search with composite keys', () => {
|
|
2980
|
+
const comparator = (a: CompositeKey, b: CompositeKey): number => {
|
|
2981
|
+
if (a.departmentId !== b.departmentId) {
|
|
2982
|
+
return a.departmentId > b.departmentId ? 1 : -1;
|
|
2983
|
+
}
|
|
2984
|
+
return a.employeeId > b.employeeId ? 1 : a.employeeId < b.employeeId ? -1 : 0;
|
|
2985
|
+
};
|
|
2986
|
+
|
|
2987
|
+
const bst = new BST<CompositeKey>([], { comparator });
|
|
2988
|
+
|
|
2989
|
+
const keys = [
|
|
2990
|
+
{ departmentId: 1, employeeId: 101 },
|
|
2991
|
+
{ departmentId: 1, employeeId: 103 },
|
|
2992
|
+
{ departmentId: 2, employeeId: 102 }
|
|
2993
|
+
];
|
|
2994
|
+
|
|
2995
|
+
keys.forEach(k => bst.add(k));
|
|
2996
|
+
|
|
2997
|
+
const searchKey = keys[1];
|
|
2998
|
+
const result = bst.search(searchKey);
|
|
2999
|
+
|
|
3000
|
+
expect(result.length).toBeGreaterThan(0);
|
|
3001
|
+
expect(result[0]).toEqual(searchKey);
|
|
3002
|
+
});
|
|
3003
|
+
|
|
3004
|
+
it('should handle deletion with composite keys', () => {
|
|
3005
|
+
const comparator = (a: CompositeKey, b: CompositeKey): number => {
|
|
3006
|
+
if (a.departmentId !== b.departmentId) {
|
|
3007
|
+
return a.departmentId > b.departmentId ? 1 : -1;
|
|
3008
|
+
}
|
|
3009
|
+
return a.employeeId > b.employeeId ? 1 : a.employeeId < b.employeeId ? -1 : 0;
|
|
3010
|
+
};
|
|
3011
|
+
|
|
3012
|
+
const bst = new BST<CompositeKey>([], { comparator });
|
|
3013
|
+
|
|
3014
|
+
const keys = [
|
|
3015
|
+
{ departmentId: 1, employeeId: 101 },
|
|
3016
|
+
{ departmentId: 1, employeeId: 103 },
|
|
3017
|
+
{ departmentId: 2, employeeId: 102 }
|
|
3018
|
+
];
|
|
3019
|
+
|
|
3020
|
+
keys.forEach(k => bst.add(k));
|
|
3021
|
+
|
|
3022
|
+
const keyToDelete = keys[1];
|
|
3023
|
+
const deleted = bst.delete(keyToDelete);
|
|
3024
|
+
|
|
3025
|
+
expect(deleted.length).toBeGreaterThan(0);
|
|
3026
|
+
expect(bst.has(keyToDelete)).toBe(false);
|
|
3027
|
+
expect(bst.size).toBe(2);
|
|
3028
|
+
});
|
|
3029
|
+
|
|
3030
|
+
it('should find ceiling/floor with composite keys', () => {
|
|
3031
|
+
const comparator = (a: CompositeKey, b: CompositeKey): number => {
|
|
3032
|
+
if (a.departmentId !== b.departmentId) {
|
|
3033
|
+
return a.departmentId > b.departmentId ? 1 : -1;
|
|
3034
|
+
}
|
|
3035
|
+
return a.employeeId > b.employeeId ? 1 : a.employeeId < b.employeeId ? -1 : 0;
|
|
3036
|
+
};
|
|
3037
|
+
|
|
3038
|
+
const bst = new BST<CompositeKey>([], { comparator });
|
|
3039
|
+
|
|
3040
|
+
const keys = [
|
|
3041
|
+
{ departmentId: 1, employeeId: 101 },
|
|
3042
|
+
{ departmentId: 1, employeeId: 105 },
|
|
3043
|
+
{ departmentId: 2, employeeId: 102 }
|
|
3044
|
+
];
|
|
3045
|
+
|
|
3046
|
+
keys.forEach(k => bst.add(k));
|
|
3047
|
+
|
|
3048
|
+
const searchKey = { departmentId: 1, employeeId: 103 };
|
|
3049
|
+
const ceiling = bst.ceiling(searchKey);
|
|
3050
|
+
const floor = bst.floor(searchKey);
|
|
3051
|
+
|
|
3052
|
+
expect(ceiling).toBeDefined();
|
|
3053
|
+
expect(floor).toBeDefined();
|
|
3054
|
+
});
|
|
3055
|
+
});
|
|
3056
|
+
|
|
3057
|
+
describe('Key-Value Storage with Comparator', () => {
|
|
3058
|
+
interface PersonKey {
|
|
3059
|
+
id: number;
|
|
3060
|
+
country: string;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
interface PersonValue {
|
|
3064
|
+
name: string;
|
|
3065
|
+
email: string;
|
|
3066
|
+
age: number;
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
it('should store and retrieve key-value pairs with composite keys', () => {
|
|
3070
|
+
const comparator = (a: PersonKey, b: PersonKey): number => {
|
|
3071
|
+
const countryCompare = a.country.localeCompare(b.country);
|
|
3072
|
+
if (countryCompare !== 0) return countryCompare;
|
|
3073
|
+
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
|
3074
|
+
};
|
|
3075
|
+
|
|
3076
|
+
const bst = new BST<PersonKey, PersonValue>([], { comparator, isMapMode: true });
|
|
3077
|
+
|
|
3078
|
+
const key1: PersonKey = { id: 1, country: 'USA' };
|
|
3079
|
+
const value1: PersonValue = { name: 'Alice', email: 'alice@example.com', age: 30 };
|
|
3080
|
+
|
|
3081
|
+
const key2: PersonKey = { id: 2, country: 'Canada' };
|
|
3082
|
+
const value2: PersonValue = { name: 'Bob', email: 'bob@example.com', age: 25 };
|
|
3083
|
+
|
|
3084
|
+
bst.add([key1, value1]);
|
|
3085
|
+
bst.add([key2, value2]);
|
|
3086
|
+
|
|
3087
|
+
expect(bst.size).toBe(2);
|
|
3088
|
+
expect(bst.get(key1)).toEqual(value1);
|
|
3089
|
+
expect(bst.get(key2)).toEqual(value2);
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
it('should update values when adding duplicate keys', () => {
|
|
3093
|
+
const comparator = (a: PersonKey, b: PersonKey): number => {
|
|
3094
|
+
const countryCompare = a.country.localeCompare(b.country);
|
|
3095
|
+
if (countryCompare !== 0) return countryCompare;
|
|
3096
|
+
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
|
3097
|
+
};
|
|
3098
|
+
|
|
3099
|
+
const bst = new BST<PersonKey, PersonValue>([], { comparator, isMapMode: true });
|
|
3100
|
+
|
|
3101
|
+
const key: PersonKey = { id: 1, country: 'USA' };
|
|
3102
|
+
const value1: PersonValue = { name: 'Alice', email: 'alice@example.com', age: 30 };
|
|
3103
|
+
const value2: PersonValue = { name: 'Alice Updated', email: 'alice.new@example.com', age: 31 };
|
|
3104
|
+
|
|
3105
|
+
bst.add([key, value1]);
|
|
3106
|
+
expect(bst.get(key)).toEqual(value1);
|
|
3107
|
+
|
|
3108
|
+
bst.add([key, value2]);
|
|
3109
|
+
expect(bst.size).toBe(1);
|
|
3110
|
+
expect(bst.get(key)).toEqual(value2);
|
|
3111
|
+
});
|
|
3112
|
+
|
|
3113
|
+
it('should retrieve all entries in sorted order by key', () => {
|
|
3114
|
+
const comparator = (a: PersonKey, b: PersonKey): number => {
|
|
3115
|
+
const countryCompare = a.country.localeCompare(b.country);
|
|
3116
|
+
if (countryCompare !== 0) return countryCompare;
|
|
3117
|
+
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
|
3118
|
+
};
|
|
3119
|
+
|
|
3120
|
+
const bst = new BST<PersonKey, PersonValue>([], { comparator, isMapMode: true });
|
|
3121
|
+
|
|
3122
|
+
const entries: Array<[PersonKey, PersonValue]> = [
|
|
3123
|
+
[
|
|
3124
|
+
{ id: 1, country: 'USA' },
|
|
3125
|
+
{ name: 'Alice', email: 'alice@usa.com', age: 30 }
|
|
3126
|
+
],
|
|
3127
|
+
[
|
|
3128
|
+
{ id: 2, country: 'Canada' },
|
|
3129
|
+
{ name: 'Bob', email: 'bob@ca.com', age: 25 }
|
|
3130
|
+
],
|
|
3131
|
+
[
|
|
3132
|
+
{ id: 1, country: 'Canada' },
|
|
3133
|
+
{ name: 'Charlie', email: 'charlie@ca.com', age: 28 }
|
|
3134
|
+
]
|
|
3135
|
+
];
|
|
3136
|
+
|
|
3137
|
+
entries.forEach(([key, value]) => bst.add([key, value]));
|
|
3138
|
+
|
|
3139
|
+
expect(bst.size).toBe(3);
|
|
3140
|
+
|
|
3141
|
+
const values: (PersonValue | undefined)[] = [];
|
|
3142
|
+
for (const [key] of entries) {
|
|
3143
|
+
const value = bst.get(key);
|
|
3144
|
+
if (value) values.push(value);
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
expect(values.length).toBeGreaterThan(0);
|
|
3148
|
+
});
|
|
3149
|
+
|
|
3150
|
+
it('should delete key-value pairs correctly', () => {
|
|
3151
|
+
const comparator = (a: PersonKey, b: PersonKey): number => {
|
|
3152
|
+
const countryCompare = a.country.localeCompare(b.country);
|
|
3153
|
+
if (countryCompare !== 0) return countryCompare;
|
|
3154
|
+
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
|
3155
|
+
};
|
|
3156
|
+
|
|
3157
|
+
const bst = new BST<PersonKey, PersonValue>([], { comparator, isMapMode: true });
|
|
3158
|
+
|
|
3159
|
+
const key1: PersonKey = { id: 1, country: 'USA' };
|
|
3160
|
+
const value1: PersonValue = { name: 'Alice', email: 'alice@example.com', age: 30 };
|
|
3161
|
+
|
|
3162
|
+
const key2: PersonKey = { id: 2, country: 'Canada' };
|
|
3163
|
+
const value2: PersonValue = { name: 'Bob', email: 'bob@example.com', age: 25 };
|
|
3164
|
+
|
|
3165
|
+
bst.add([key1, value1]);
|
|
3166
|
+
bst.add([key2, value2]);
|
|
3167
|
+
|
|
3168
|
+
bst.delete(key1);
|
|
3169
|
+
|
|
3170
|
+
expect(bst.has(key1)).toBe(false);
|
|
3171
|
+
expect(bst.has(key2)).toBe(true);
|
|
3172
|
+
expect(bst.size).toBe(1);
|
|
3173
|
+
expect(bst.get(key1)).toBeUndefined();
|
|
3174
|
+
});
|
|
3175
|
+
|
|
3176
|
+
it('should search and retrieve values with composite keys', () => {
|
|
3177
|
+
const comparator = (a: PersonKey, b: PersonKey): number => {
|
|
3178
|
+
const countryCompare = a.country.localeCompare(b.country);
|
|
3179
|
+
if (countryCompare !== 0) return countryCompare;
|
|
3180
|
+
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
|
3181
|
+
};
|
|
3182
|
+
|
|
3183
|
+
const bst = new BST<PersonKey, PersonValue>([], { comparator, isMapMode: true });
|
|
3184
|
+
|
|
3185
|
+
const entries: Array<[PersonKey, PersonValue]> = [
|
|
3186
|
+
[
|
|
3187
|
+
{ id: 1, country: 'USA' },
|
|
3188
|
+
{ name: 'Alice', email: 'alice@usa.com', age: 30 }
|
|
3189
|
+
],
|
|
3190
|
+
[
|
|
3191
|
+
{ id: 2, country: 'USA' },
|
|
3192
|
+
{ name: 'Amy', email: 'amy@usa.com', age: 28 }
|
|
3193
|
+
],
|
|
3194
|
+
[
|
|
3195
|
+
{ id: 1, country: 'Canada' },
|
|
3196
|
+
{ name: 'Bob', email: 'bob@ca.com', age: 25 }
|
|
3197
|
+
]
|
|
3198
|
+
];
|
|
3199
|
+
|
|
3200
|
+
entries.forEach(([key, value]) => bst.add([key, value]));
|
|
3201
|
+
|
|
3202
|
+
const searchKey: PersonKey = entries[1][0];
|
|
3203
|
+
const value = bst.get(searchKey);
|
|
3204
|
+
|
|
3205
|
+
expect(value).toEqual({ name: 'Amy', email: 'amy@usa.com', age: 28 });
|
|
3206
|
+
});
|
|
3207
|
+
|
|
3208
|
+
it('should map over key-value pairs maintaining comparator order', () => {
|
|
3209
|
+
const comparator = (a: PersonKey, b: PersonKey): number => {
|
|
3210
|
+
const countryCompare = a.country.localeCompare(b.country);
|
|
3211
|
+
if (countryCompare !== 0) return countryCompare;
|
|
3212
|
+
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
|
3213
|
+
};
|
|
3214
|
+
|
|
3215
|
+
const bst = new BST<PersonKey, PersonValue>([], { comparator, isMapMode: true });
|
|
3216
|
+
|
|
3217
|
+
const entries: Array<[PersonKey, PersonValue]> = [
|
|
3218
|
+
[
|
|
3219
|
+
{ id: 1, country: 'USA' },
|
|
3220
|
+
{ name: 'Alice', email: 'alice@usa.com', age: 30 }
|
|
3221
|
+
],
|
|
3222
|
+
[
|
|
3223
|
+
{ id: 2, country: 'Canada' },
|
|
3224
|
+
{ name: 'Bob', email: 'bob@ca.com', age: 25 }
|
|
3225
|
+
]
|
|
3226
|
+
];
|
|
3227
|
+
|
|
3228
|
+
entries.forEach(([key, value]) => bst.add([key, value]));
|
|
3229
|
+
|
|
3230
|
+
const mapped = bst.map((value, key) => [key, value?.name], { comparator, isMapMode: false });
|
|
3231
|
+
|
|
3232
|
+
expect(mapped.size).toBe(2);
|
|
3233
|
+
const names = Array.from(mapped.values());
|
|
3234
|
+
expect(names).toContain('Alice');
|
|
3235
|
+
expect(names).toContain('Bob');
|
|
3236
|
+
});
|
|
3237
|
+
|
|
3238
|
+
it('should perform range search on key-value pairs', () => {
|
|
3239
|
+
const comparator = (a: number, b: number): number => {
|
|
3240
|
+
return a > b ? 1 : a < b ? -1 : 0;
|
|
3241
|
+
};
|
|
3242
|
+
|
|
3243
|
+
const bst = new BST<number, string>([], { comparator, isMapMode: true });
|
|
3244
|
+
|
|
3245
|
+
for (let i = 1; i <= 10; i++) {
|
|
3246
|
+
bst.add([i, `value-${i}`]);
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
const result = bst.rangeSearch([3, 7]);
|
|
3250
|
+
|
|
3251
|
+
expect(result.length).toBeGreaterThan(0);
|
|
3252
|
+
result.forEach(key => {
|
|
3253
|
+
expect(key).toBeGreaterThanOrEqual(3);
|
|
3254
|
+
expect(key).toBeLessThanOrEqual(7);
|
|
3255
|
+
});
|
|
3256
|
+
});
|
|
3257
|
+
|
|
3258
|
+
it('should maintain sorted iteration over key-value pairs', () => {
|
|
3259
|
+
const comparator = (a: { priority: number; id: number }, b: { priority: number; id: number }): number => {
|
|
3260
|
+
if (a.priority !== b.priority) {
|
|
3261
|
+
return a.priority > b.priority ? 1 : -1;
|
|
3262
|
+
}
|
|
3263
|
+
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
|
3264
|
+
};
|
|
3265
|
+
|
|
3266
|
+
type TaskKey = { priority: number; id: number };
|
|
3267
|
+
type TaskValue = { title: string; completed: boolean };
|
|
3268
|
+
|
|
3269
|
+
const bst = new BST<TaskKey, TaskValue>([], { comparator, isMapMode: true });
|
|
3270
|
+
|
|
3271
|
+
const tasks: Array<[TaskKey, TaskValue]> = [
|
|
3272
|
+
[
|
|
3273
|
+
{ priority: 2, id: 1 },
|
|
3274
|
+
{ title: 'Task 1', completed: false }
|
|
3275
|
+
],
|
|
3276
|
+
[
|
|
3277
|
+
{ priority: 1, id: 2 },
|
|
3278
|
+
{ title: 'Task 2', completed: false }
|
|
3279
|
+
],
|
|
3280
|
+
[
|
|
3281
|
+
{ priority: 3, id: 3 },
|
|
3282
|
+
{ title: 'Task 3', completed: true }
|
|
3283
|
+
],
|
|
3284
|
+
[
|
|
3285
|
+
{ priority: 1, id: 1 },
|
|
3286
|
+
{ title: 'Task 4', completed: false }
|
|
3287
|
+
]
|
|
3288
|
+
];
|
|
3289
|
+
|
|
3290
|
+
tasks.forEach(([key, value]) => bst.add([key, value]));
|
|
3291
|
+
|
|
3292
|
+
expect(bst.size).toBe(4);
|
|
3293
|
+
|
|
3294
|
+
const inOrder = bst.dfs(node => node?.key.priority);
|
|
3295
|
+
for (let i = 1; i < inOrder.length; i++) {
|
|
3296
|
+
expect(inOrder[i]! >= inOrder[i - 1]!).toBe(true);
|
|
3297
|
+
}
|
|
3298
|
+
});
|
|
3299
|
+
});
|
|
3300
|
+
});
|
|
3301
|
+
|
|
2689
3302
|
describe('classic use', () => {
|
|
2690
3303
|
it('@example basic BST creation and add operation', () => {
|
|
2691
3304
|
// Create a simple BST with numeric keys
|
|
@@ -2823,7 +3436,7 @@ describe('classic use', () => {
|
|
|
2823
3436
|
return findFirstCommon(path1, path2);
|
|
2824
3437
|
};
|
|
2825
3438
|
|
|
2826
|
-
function findFirstCommon(arr1: number[], arr2: number[]): number | undefined {
|
|
3439
|
+
function findFirstCommon(arr1: (number | undefined)[], arr2: (number | undefined)[]): number | undefined {
|
|
2827
3440
|
for (const num of arr1) {
|
|
2828
3441
|
if (arr2.indexOf(num) !== -1) {
|
|
2829
3442
|
return num;
|