PersistentMap/TestProject1/OrderedQueriesTest.cs
linus björnstam 7bea233edc Did some code cleanup,
added some extra thingies.
switched to spans. Let google gemini do whatever it wanted..
2026-04-16 11:51:38 +02:00

181 lines
6.4 KiB
C#

using System.Linq;
using Xunit;
using PersistentMap;
namespace PersistentMap.Tests
{
public class BTreeExtendedOperationsTests
{
// Helper to quickly spin up a populated map
private TransientMap<int, string, IntStrategy> CreateMap(params int[] keys)
{
var map = BaseOrderedMap<int, string, IntStrategy>.CreateTransient(new IntStrategy());
foreach (var key in keys)
{
map.Set(key, $"val_{key}");
}
return map;
}
[Fact]
public void MinMax_OnEmptyTree_ReturnsFalse()
{
var map = CreateMap();
bool hasMin = map.TryGetMin(out int minKey, out string minVal);
bool hasMax = map.TryGetMax(out int maxKey, out string maxVal);
Assert.False(hasMin);
Assert.False(hasMax);
Assert.Equal(default, minKey);
Assert.Equal(default, maxKey);
}
[Fact]
public void MinMax_OnPopulatedTree_ReturnsCorrectExtremes()
{
var map = CreateMap(50, 10, 40, 20, 30); // Insert out of order
bool hasMin = map.TryGetMin(out int minKey, out string minVal);
bool hasMax = map.TryGetMax(out int maxKey, out string maxVal);
Assert.True(hasMin);
Assert.Equal(10, minKey);
Assert.Equal("val_10", minVal);
Assert.True(hasMax);
Assert.Equal(50, maxKey);
Assert.Equal("val_50", maxVal);
}
[Theory]
[InlineData(20, true, 30)] // Exact match, get next
[InlineData(25, true, 30)] // Missing key, gets first greater
[InlineData(50, false, 0)] // Max element has no successor
[InlineData(60, false, 0)] // Out of bounds high
public void Successor_ReturnsCorrectNextKey(int searchKey, bool expectedSuccess, int expectedNextKey)
{
var map = CreateMap(10, 20, 30, 40, 50);
bool success = map.TryGetSuccessor(searchKey, out int nextKey, out string nextVal);
Assert.Equal(expectedSuccess, success);
if (expectedSuccess)
{
Assert.Equal(expectedNextKey, nextKey);
Assert.Equal($"val_{expectedNextKey}", nextVal);
}
}
[Theory]
[InlineData(40, true, 30)] // Exact match, get previous
[InlineData(35, true, 30)] // Missing key, gets largest smaller
[InlineData(10, false, 0)] // Min element has no predecessor
[InlineData(5, false, 0)] // Out of bounds low
public void Predecessor_ReturnsCorrectPreviousKey(int searchKey, bool expectedSuccess, int expectedPrevKey)
{
var map = CreateMap(10, 20, 30, 40, 50);
bool success = map.TryGetPredecessor(searchKey, out int prevKey, out string prevVal);
Assert.Equal(expectedSuccess, success);
if (expectedSuccess)
{
Assert.Equal(expectedPrevKey, prevKey);
Assert.Equal($"val_{expectedPrevKey}", prevVal);
}
}
[Fact]
public void SuccessorPredecessor_CrossNodeBoundaries_WorksCorrectly()
{
// Insert 200 elements to guarantee leaf node splits (Capacity is 64)
// and internal node creation.
var keys = Enumerable.Range(1, 200).ToArray();
var map = CreateMap(keys);
// Test boundaries between multiple leaves
for (int i = 1; i < 200; i++)
{
// Successor
bool sFound = map.TryGetSuccessor(i, out int next, out _);
Assert.True(sFound);
Assert.Equal(i + 1, next);
// Predecessor
bool pFound = map.TryGetPredecessor(i + 1, out int prev, out _);
Assert.True(pFound);
Assert.Equal(i, prev);
}
}
[Fact]
public void SetOperations_Intersect_ReturnsCommonElements()
{
var mapA = CreateMap(1, 2, 3, 4, 5);
var mapB = CreateMap(4, 5, 6, 7, 8);
var intersect = mapA.Intersect(mapB).Select(kvp => kvp.Key).ToArray();
Assert.Equal(new[] { 4, 5 }, intersect);
}
[Fact]
public void SetOperations_Except_ReturnsElementsOnlyInFirstMap()
{
var mapA = CreateMap(1, 2, 3, 4, 5);
var mapB = CreateMap(4, 5, 6, 7, 8);
var aExceptB = mapA.Except(mapB).Select(kvp => kvp.Key).ToArray();
var bExceptA = mapB.Except(mapA).Select(kvp => kvp.Key).ToArray();
Assert.Equal(new[] { 1, 2, 3 }, aExceptB);
Assert.Equal(new[] { 6, 7, 8 }, bExceptA);
}
[Fact]
public void SetOperations_SymmetricExcept_ReturnsNonOverlappingElements()
{
var mapA = CreateMap(1, 2, 3, 4, 5);
var mapB = CreateMap(4, 5, 6, 7, 8);
var symmetric = mapA.SymmetricExcept(mapB).Select(kvp => kvp.Key).ToArray();
// Should return elements exclusively in A or B, but not both.
// Expected sorted naturally: 1, 2, 3, 6, 7, 8
Assert.Equal(new[] { 1, 2, 3, 6, 7, 8 }, symmetric);
}
[Fact]
public void SetOperations_WithEmptyMaps_HandleGracefully()
{
var populatedMap = CreateMap(1, 2, 3);
var emptyMap = CreateMap();
// Intersect with empty is empty
Assert.Empty(populatedMap.Intersect(emptyMap));
Assert.Empty(emptyMap.Intersect(populatedMap));
// Populated Except empty is Populated
Assert.Equal(new[] { 1, 2, 3 }, populatedMap.Except(emptyMap).Select(k => k.Key));
// Empty Except Populated is empty
Assert.Empty(emptyMap.Except(populatedMap));
// Symmetric Except with empty is just the populated map
Assert.Equal(new[] { 1, 2, 3 }, populatedMap.SymmetricExcept(emptyMap).Select(k => k.Key));
Assert.Equal(new[] { 1, 2, 3 }, emptyMap.SymmetricExcept(populatedMap).Select(k => k.Key));
}
[Fact]
public void SetOperations_CompleteOverlap_HandlesCorrectly()
{
var mapA = CreateMap(1, 2, 3);
var mapB = CreateMap(1, 2, 3);
Assert.Equal(new[] { 1, 2, 3 }, mapA.Intersect(mapB).Select(k => k.Key));
Assert.Empty(mapA.Except(mapB));
Assert.Empty(mapA.SymmetricExcept(mapB));
}
}
}