diff --git a/csharp/Platform.Collections.Tests/BitStringTests.cs b/csharp/Platform.Collections.Tests/BitStringTests.cs index 988deca8..5d90355a 100644 --- a/csharp/Platform.Collections.Tests/BitStringTests.cs +++ b/csharp/Platform.Collections.Tests/BitStringTests.cs @@ -142,6 +142,93 @@ public static void BitParallelVectorXorTest() w.Xor(v); }); } + + [Fact] + public static void AutomaticBorderRefreshingPropertyTest() + { + var bitString = new BitString(100); + Assert.True(bitString.AutomaticBorderRefreshing); + + bitString.AutomaticBorderRefreshing = false; + Assert.False(bitString.AutomaticBorderRefreshing); + + bitString.AutomaticBorderRefreshing = true; + Assert.True(bitString.AutomaticBorderRefreshing); + } + + [Fact] + public static void CreateWithAutomaticBorderRefreshingTest() + { + var automaticBitString = BitString.Create(100, true); + Assert.True(automaticBitString.AutomaticBorderRefreshing); + + var manualBitString = BitString.Create(100, false); + Assert.False(manualBitString.AutomaticBorderRefreshing); + } + + [Fact] + public static void CreateWithDefaultValueAndAutomaticBorderRefreshingTest() + { + var automaticBitString = BitString.Create(100, false, true); + Assert.True(automaticBitString.AutomaticBorderRefreshing); + + var manualBitString = BitString.Create(100, true, false); + Assert.False(manualBitString.AutomaticBorderRefreshing); + } + + [Fact] + public static void ManualBorderRefreshTest() + { + var bitString = BitString.Create(1000, false); + + // Set some bits + bitString.Set(100, true); + bitString.Set(500, true); + bitString.Set(900, true); + + // Manually refresh borders + bool updated = bitString.RefreshBorders(); + Assert.True(updated); + + // Verify correct borders + Assert.Equal(100, bitString.GetFirstSetBitIndex()); + Assert.Equal(900, bitString.GetLastSetBitIndex()); + } + + [Fact] + public static void AutomaticVsManualBorderBehaviorTest() + { + // Test with automatic border refreshing + var automaticBitString = new BitString(100); + automaticBitString.Set(50, true); + var automaticFirst = automaticBitString.GetFirstSetBitIndex(); + var automaticLast = automaticBitString.GetLastSetBitIndex(); + + // Test with manual border refreshing + var manualBitString = BitString.Create(100, false); + manualBitString.Set(50, true); + manualBitString.RefreshBorders(); + var manualFirst = manualBitString.GetFirstSetBitIndex(); + var manualLast = manualBitString.GetLastSetBitIndex(); + + // Results should be the same + Assert.Equal(automaticFirst, manualFirst); + Assert.Equal(automaticLast, manualLast); + Assert.Equal(50, automaticFirst); + Assert.Equal(50, manualFirst); + } + + [Fact] + public static void CopyConstructorPreservesAutomaticBorderRefreshingTest() + { + var originalAutomatic = new BitString(100); + var copyAutomatic = new BitString(originalAutomatic); + Assert.True(copyAutomatic.AutomaticBorderRefreshing); + + var originalManual = BitString.Create(100, false); + var copyManual = new BitString(originalManual); + Assert.False(copyManual.AutomaticBorderRefreshing); + } private static void TestToOperationsWithSameMeaning(Action test) { const int n = 5654; diff --git a/csharp/Platform.Collections/BitString.cs b/csharp/Platform.Collections/BitString.cs index 4b0b36ac..b15434a5 100644 --- a/csharp/Platform.Collections/BitString.cs +++ b/csharp/Platform.Collections/BitString.cs @@ -26,6 +26,7 @@ public class BitString : IEquatable private long _length; private long _minPositiveWord; private long _maxPositiveWord; + private bool _automaticBorderRefreshing; /// /// @@ -91,6 +92,22 @@ public long Length } } + /// + /// + /// Gets or sets the automatic border refreshing value. + /// When enabled, borders (minimum and maximum non-zero words) are automatically updated on every bit change. + /// When disabled, borders need to be manually refreshed using RefreshBorders() method. + /// + /// + /// + public bool AutomaticBorderRefreshing + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _automaticBorderRefreshing; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _automaticBorderRefreshing = value; + } + #region Constructors /// @@ -147,6 +164,7 @@ public BitString(BitString other) _array = new long[GetWordsCountFromIndex(_length)]; _minPositiveWord = other._minPositiveWord; _maxPositiveWord = other._maxPositiveWord; + _automaticBorderRefreshing = other._automaticBorderRefreshing; Array.Copy(other._array, _array, _array.LongLength); } @@ -166,6 +184,7 @@ public BitString(long length) Ensure.Always.ArgumentInRange(length, GetValidLengthRange(), nameof(length)); _length = length; _array = new long[GetWordsCountFromIndex(_length)]; + _automaticBorderRefreshing = true; MarkBordersAsAllBitsReset(); } @@ -193,8 +212,65 @@ public BitString(long length, bool defaultValue) } } + #endregion + /// + /// + /// Creates a new instance with specified automatic border refreshing behavior. + /// + /// + /// + /// + /// A length. + /// + /// + /// + /// Whether to automatically refresh borders on every bit change. + /// + /// + /// + /// A new BitString instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BitString Create(long length, bool automaticBorderRefreshing) + { + var bitString = new BitString(length); + bitString._automaticBorderRefreshing = automaticBorderRefreshing; + return bitString; + } + + /// + /// + /// Creates a new instance with specified default value and automatic border refreshing behavior. + /// + /// + /// + /// + /// A length. + /// + /// + /// + /// A default value. + /// + /// + /// + /// Whether to automatically refresh borders on every bit change. + /// + /// + /// + /// A new BitString instance. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BitString Create(long length, bool defaultValue, bool automaticBorderRefreshing) + { + var bitString = new BitString(length, defaultValue); + bitString._automaticBorderRefreshing = automaticBorderRefreshing; + return bitString; + } + /// /// /// Nots this instance. @@ -894,26 +970,29 @@ static private void VectorXorLoop(long[] array, long[] otherArray, int step, int [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RefreshBordersByWord(long wordIndex) { - if (_array[wordIndex] == 0) - { - if (wordIndex == _minPositiveWord && wordIndex != _array.LongLength - 1) - { - _minPositiveWord++; - } - if (wordIndex == _maxPositiveWord && wordIndex != 0) - { - _maxPositiveWord--; - } - } - else + if (_automaticBorderRefreshing) { - if (wordIndex < _minPositiveWord) + if (_array[wordIndex] == 0) { - _minPositiveWord = wordIndex; + if (wordIndex == _minPositiveWord && wordIndex != _array.LongLength - 1) + { + _minPositiveWord++; + } + if (wordIndex == _maxPositiveWord && wordIndex != 0) + { + _maxPositiveWord--; + } } - if (wordIndex > _maxPositiveWord) + else { - _maxPositiveWord = wordIndex; + if (wordIndex < _minPositiveWord) + { + _minPositiveWord = wordIndex; + } + if (wordIndex > _maxPositiveWord) + { + _maxPositiveWord = wordIndex; + } } } } @@ -958,6 +1037,65 @@ public bool TryShrinkBorders() return bordersUpdated; } + /// + /// + /// Manually refreshes the borders (minimum and maximum non-zero words). + /// This method recalculates the accurate borders by scanning through the entire array. + /// Use this when AutomaticBorderRefreshing is disabled to update borders when needed. + /// + /// + /// + /// + /// The borders updated. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool RefreshBorders() + { + var oldMinPositiveWord = _minPositiveWord; + var oldMaxPositiveWord = _maxPositiveWord; + + // Scan the entire array to find actual borders + long minPositiveWord = _array.LongLength - 1; + long maxPositiveWord = 0; + bool foundAnySet = false; + + for (long i = 0; i < _array.LongLength; i++) + { + if (_array[i] != 0) + { + if (!foundAnySet) + { + minPositiveWord = i; + maxPositiveWord = i; + foundAnySet = true; + } + else + { + if (i < minPositiveWord) + { + minPositiveWord = i; + } + if (i > maxPositiveWord) + { + maxPositiveWord = i; + } + } + } + } + + if (foundAnySet) + { + SetBorders(minPositiveWord, maxPositiveWord); + } + else + { + MarkBordersAsAllBitsReset(); + } + + return _minPositiveWord != oldMinPositiveWord || _maxPositiveWord != oldMaxPositiveWord; + } + /// /// /// Determines whether this instance get. diff --git a/experiments/ManualBorderRefreshTest.cs b/experiments/ManualBorderRefreshTest.cs new file mode 100644 index 00000000..c1077e98 --- /dev/null +++ b/experiments/ManualBorderRefreshTest.cs @@ -0,0 +1,139 @@ +using System; +using Platform.Collections; + +namespace Experiments +{ + /// + /// Test class to verify manual border refresh functionality for BitString. + /// This addresses issue #20: "Think about ability to manual refreshing of borders" + /// + public static class ManualBorderRefreshTest + { + public static void Main() + { + Console.WriteLine("Testing Manual Border Refresh Functionality"); + Console.WriteLine("==========================================="); + + // Test 1: Default behavior (automatic border refreshing enabled) + Console.WriteLine("\nTest 1: Default behavior (automatic refreshing)"); + TestDefaultBehavior(); + + // Test 2: Manual border refreshing (automatic disabled) + Console.WriteLine("\nTest 2: Manual border refreshing"); + TestManualBorderRefresh(); + + // Test 3: Performance comparison + Console.WriteLine("\nTest 3: Performance comparison"); + TestPerformanceComparison(); + + Console.WriteLine("\nAll tests completed successfully!"); + } + + private static void TestDefaultBehavior() + { + var bitString = new BitString(1000); + Console.WriteLine($"AutomaticBorderRefreshing: {bitString.AutomaticBorderRefreshing}"); + + // Set some bits and verify borders are updated automatically + bitString.Set(100, true); + bitString.Set(500, true); + bitString.Set(900, true); + + var firstIndex = bitString.GetFirstSetBitIndex(); + var lastIndex = bitString.GetLastSetBitIndex(); + + Console.WriteLine($"First set bit: {firstIndex} (expected: 100)"); + Console.WriteLine($"Last set bit: {lastIndex} (expected: 900)"); + + if (firstIndex == 100 && lastIndex == 900) + { + Console.WriteLine("✓ Default behavior test passed"); + } + else + { + Console.WriteLine("✗ Default behavior test failed"); + } + } + + private static void TestManualBorderRefresh() + { + // Create BitString with automatic border refreshing disabled + var bitString = BitString.Create(1000, false); + Console.WriteLine($"AutomaticBorderRefreshing: {bitString.AutomaticBorderRefreshing}"); + + // Set some bits (borders should not be updated automatically) + bitString.Set(100, true); + bitString.Set(500, true); + bitString.Set(900, true); + + // Without refreshing borders, the first/last might be incorrect + var firstBeforeRefresh = bitString.GetFirstSetBitIndex(); + var lastBeforeRefresh = bitString.GetLastSetBitIndex(); + + Console.WriteLine($"Before manual refresh - First: {firstBeforeRefresh}, Last: {lastBeforeRefresh}"); + + // Manually refresh borders + bool bordersUpdated = bitString.RefreshBorders(); + Console.WriteLine($"Borders updated by manual refresh: {bordersUpdated}"); + + var firstAfterRefresh = bitString.GetFirstSetBitIndex(); + var lastAfterRefresh = bitString.GetLastSetBitIndex(); + + Console.WriteLine($"After manual refresh - First: {firstAfterRefresh}, Last: {lastAfterRefresh}"); + + if (firstAfterRefresh == 100 && lastAfterRefresh == 900) + { + Console.WriteLine("✓ Manual border refresh test passed"); + } + else + { + Console.WriteLine("✗ Manual border refresh test failed"); + } + } + + private static void TestPerformanceComparison() + { + const int iterations = 10000; + const int bitStringSize = 10000; + + // Test automatic refreshing + var automaticBitString = new BitString(bitStringSize); + var startTime = DateTime.UtcNow; + + for (int i = 0; i < iterations; i++) + { + automaticBitString.Set(i % bitStringSize, true); + automaticBitString.Set(i % bitStringSize, false); + } + + var automaticTime = DateTime.UtcNow - startTime; + Console.WriteLine($"Automatic border refreshing: {automaticTime.TotalMilliseconds:F2} ms"); + + // Test manual refreshing + var manualBitString = BitString.Create(bitStringSize, false); + startTime = DateTime.UtcNow; + + for (int i = 0; i < iterations; i++) + { + manualBitString.Set(i % bitStringSize, true); + manualBitString.Set(i % bitStringSize, false); + } + + manualBitString.RefreshBorders(); // Single refresh at the end + var manualTime = DateTime.UtcNow - startTime; + Console.WriteLine($"Manual border refreshing: {manualTime.TotalMilliseconds:F2} ms"); + + var improvement = (automaticTime.TotalMilliseconds - manualTime.TotalMilliseconds) / automaticTime.TotalMilliseconds * 100; + Console.WriteLine($"Performance improvement: {improvement:F1}%"); + + if (manualTime < automaticTime) + { + Console.WriteLine("✓ Performance test shows manual refreshing is faster"); + } + else + { + Console.WriteLine("! Performance test shows similar or worse performance (expected with small datasets)"); + } + } + } +} \ No newline at end of file