diff --git a/BITSTRING_INDEXING.md b/BITSTRING_INDEXING.md new file mode 100644 index 000000000..7a492fb26 --- /dev/null +++ b/BITSTRING_INDEXING.md @@ -0,0 +1,154 @@ +# BitString Indexing Implementation + +This document describes the bitstring indexing implementation added to Platform.Data.Doublets as an alternative/additional indexing mechanism to the existing tree-based approaches. + +## Overview + +Bitstring indexing uses bit arrays (BitArray) to efficiently store and query relationships between links. This approach is particularly effective for: + +- High-performance bitwise operations (AND, OR, XOR) +- Efficient set intersection and union operations +- Memory-efficient storage for sparse data relationships +- Direct hardware-accelerated bitwise computations + +## Architecture + +### Core Components + +1. **IndexTreeType.BitStringIndex** - New enum value (4) added to support bitstring indexing +2. **IBitStringTreeMethods** - Interface extending ILinksTreeMethods with bitstring-specific operations +3. **BitStringIndexMethodsBase** - Base implementation for Split memory model +4. **LinksBitStringIndexMethodsBase** - Base implementation for United memory model + +### Implementation Classes + +#### Split Memory Model +- `InternalLinksBitStringIndexMethods` - For internal links data +- `ExternalLinksBitStringIndexMethods` - For external links indices + +#### United Memory Model +- `LinksSourcesBitStringIndexMethods` - For source-based indexing +- `LinksTargetsBitStringIndexMethods` - For target-based indexing + +## Key Features + +### Bitwise Operations +```csharp +// Perform efficient set operations on relationships +BitArray intersection = bitStringMethods.BitwiseAnd(key1, key2); +BitArray union = bitStringMethods.BitwiseOr(key1, key2); +BitArray difference = bitStringMethods.BitwiseXor(key1, key2); +``` + +### Bit Manipulation +```csharp +// Set/get individual bits representing relationships +bitStringMethods.SetBit(linkId, position, true); +bool hasRelationship = bitStringMethods.GetBit(linkId, position); +``` + +### Usage Tracking +```csharp +// Count and enumerate link usages +int usageCount = bitStringMethods.CountSetBits(linkId); +bitStringMethods.EachUsage(rootLink, handler); +``` + +### Dynamic Resizing +- BitArrays automatically resize when new relationships exceed current capacity +- Exponential growth strategy (2x) for efficient memory management +- Default initial size: 1024 bits (configurable) + +## Usage Examples + +### Basic Setup +```csharp +var memory = new HeapResizableDirectMemory(); +var constants = new LinksConstants(enableExternalReferencesSupport: true); + +unsafe +{ + var header = memory.AllocateOrReserve(sizeof(LinksHeader)); + var links = memory.AllocateOrReserve(sizeof(RawLink) * 10); + + var bitStringMethods = new LinksSourcesBitStringIndexMethods( + constants, (byte*)links, (byte*)header); + + // Use bitstring operations... + + memory.Free(); +} +``` + +### Relationship Management +```csharp +// Establish relationships +bitStringMethods.SetBit(sourceLink, relationshipId, true); +bitStringMethods.SetBit(targetLink, relationshipId, true); + +// Search for common relationships +var commonRelation = bitStringMethods.Search(sourceLink, targetLink); +``` + +### Attach/Detach Operations +```csharp +// Attach child to parent +var parent = parentLink; +bitStringMethods.Attach(ref parent, childLink); + +// Detach child from parent +bitStringMethods.Detach(ref parent, childLink); +``` + +## Performance Characteristics + +### Advantages +- **O(1)** bit set/get operations +- **Hardware-accelerated** bitwise operations (AND, OR, XOR) +- **Memory efficient** for sparse relationship storage +- **Parallel processing** friendly - bitwise operations can be vectorized + +### Considerations +- **Memory overhead** for dense relationships (compared to tree structures) +- **Not sorted** - relationships are accessed by position, not in sorted order +- **Dynamic resizing cost** when BitArrays need to expand + +## Integration with Existing Code + +The bitstring indexing implementation follows the same patterns as existing tree methods: + +1. Implements the same `ILinksTreeMethods` interface +2. Provides all required operations: CountUsages, Search, EachUsage, Attach, Detach +3. Can be used as a drop-in replacement for tree-based indexing +4. Supports both Split and United memory models + +## Testing + +Comprehensive unit tests are provided in `BitStringIndexMethodsTests.cs` covering: + +- Basic bitstring operations (SetBit, GetBit, CountSetBits) +- Bitwise operations (AND, OR, XOR) +- Attach/Detach operations +- Search functionality +- Usage enumeration + +## Future Enhancements + +Potential improvements for future versions: + +1. **Compression** - Implement run-length encoding or other compression schemes +2. **Persistence** - Add support for saving/loading bitstring indices to/from disk +3. **Hybrid indexing** - Combine bitstring with tree indexing for optimal performance +4. **SIMD optimization** - Leverage SIMD instructions for even faster bitwise operations +5. **Bloom filters** - Add probabilistic membership testing for large datasets + +## Conclusion + +The bitstring indexing implementation provides a high-performance alternative to tree-based indexing, particularly suitable for applications requiring: + +- Fast set operations on relationships +- Efficient sparse data storage +- Hardware-accelerated bitwise computations +- Simple relationship presence/absence queries + +This implementation maintains full compatibility with the existing Platform.Data.Doublets architecture while offering unique performance characteristics for specific use cases. \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets.Tests/BitStringIndexMethodsTests.cs b/csharp/Platform.Data.Doublets.Tests/BitStringIndexMethodsTests.cs new file mode 100644 index 000000000..f2516979b --- /dev/null +++ b/csharp/Platform.Data.Doublets.Tests/BitStringIndexMethodsTests.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections; +using Platform.Data.Doublets.Memory; +using Platform.Data.Doublets.Memory.United.Generic; +using Platform.Memory; +using Xunit; + +namespace Platform.Data.Doublets.Tests +{ + public static class BitStringIndexMethodsTests + { + [Fact] + public static void BitStringBasicOperationsTest() + { + const ulong firstLink = 1ul; + const ulong secondLink = 2ul; + const ulong thirdLink = 3ul; + const int bitPosition1 = 0; + const int bitPosition2 = 5; + const int bitPosition3 = 10; + + var memory = new HeapResizableDirectMemory(); + var constants = new LinksConstants(enableExternalReferencesSupport: true); + + unsafe + { + var header = memory.AllocateOrReserve(sizeof(LinksHeader)); + var links = memory.AllocateOrReserve(sizeof(RawLink) * 3); + + var bitStringMethods = new LinksSourcesBitStringIndexMethods(constants, (byte*)links, (byte*)header); + + // Test SetBit and GetBit + bitStringMethods.SetBit(firstLink, bitPosition1, true); + bitStringMethods.SetBit(firstLink, bitPosition2, true); + bitStringMethods.SetBit(secondLink, bitPosition1, true); + bitStringMethods.SetBit(secondLink, bitPosition3, true); + + Assert.True(bitStringMethods.GetBit(firstLink, bitPosition1)); + Assert.True(bitStringMethods.GetBit(firstLink, bitPosition2)); + Assert.False(bitStringMethods.GetBit(firstLink, bitPosition3)); + + Assert.True(bitStringMethods.GetBit(secondLink, bitPosition1)); + Assert.False(bitStringMethods.GetBit(secondLink, bitPosition2)); + Assert.True(bitStringMethods.GetBit(secondLink, bitPosition3)); + + // Test CountSetBits + Assert.Equal(2, bitStringMethods.CountSetBits(firstLink)); + Assert.Equal(2, bitStringMethods.CountSetBits(secondLink)); + Assert.Equal(0, bitStringMethods.CountSetBits(thirdLink)); + + // Test BitwiseAnd + var andResult = bitStringMethods.BitwiseAnd(firstLink, secondLink); + Assert.True(andResult[bitPosition1]); // Both have bit 0 set + Assert.False(andResult[bitPosition2]); // Only firstLink has bit 5 set + Assert.False(andResult[bitPosition3]); // Only secondLink has bit 10 set + + // Test BitwiseOr + var orResult = bitStringMethods.BitwiseOr(firstLink, secondLink); + Assert.True(orResult[bitPosition1]); // Both have bit 0 set + Assert.True(orResult[bitPosition2]); // firstLink has bit 5 set + Assert.True(orResult[bitPosition3]); // secondLink has bit 10 set + + // Test BitwiseXor + var xorResult = bitStringMethods.BitwiseXor(firstLink, secondLink); + Assert.False(xorResult[bitPosition1]); // Both have bit 0 set (XOR = false) + Assert.True(xorResult[bitPosition2]); // Only firstLink has bit 5 set + Assert.True(xorResult[bitPosition3]); // Only secondLink has bit 10 set + + memory.Free(); + } + } + + [Fact] + public static void BitStringAttachDetachTest() + { + const ulong rootLink = 1ul; + const ulong childLink1 = 2ul; + const ulong childLink2 = 3ul; + const ulong childLink3 = 4ul; + + var memory = new HeapResizableDirectMemory(); + var constants = new LinksConstants(enableExternalReferencesSupport: true); + + unsafe + { + var header = memory.AllocateOrReserve(sizeof(LinksHeader)); + var links = memory.AllocateOrReserve(sizeof(RawLink) * 4); + + var bitStringMethods = new LinksTargetsBitStringIndexMethods(constants, (byte*)links, (byte*)header); + + // Test Attach + var root = rootLink; + bitStringMethods.Attach(ref root, childLink1); + bitStringMethods.Attach(ref root, childLink2); + bitStringMethods.Attach(ref root, childLink3); + + // Test CountUsages + Assert.Equal(3ul, bitStringMethods.CountUsages(rootLink)); + + // Test GetBitString + var bitString = bitStringMethods.GetBitString(rootLink); + Assert.True(bitString[int.CreateTruncating(childLink1) - 1]); + Assert.True(bitString[int.CreateTruncating(childLink2) - 1]); + Assert.True(bitString[int.CreateTruncating(childLink3) - 1]); + + // Test Detach + bitStringMethods.Detach(ref root, childLink2); + Assert.Equal(2ul, bitStringMethods.CountUsages(rootLink)); + + var updatedBitString = bitStringMethods.GetBitString(rootLink); + Assert.True(updatedBitString[int.CreateTruncating(childLink1) - 1]); + Assert.False(updatedBitString[int.CreateTruncating(childLink2) - 1]); + Assert.True(updatedBitString[int.CreateTruncating(childLink3) - 1]); + + memory.Free(); + } + } + + [Fact] + public static void BitStringSearchTest() + { + const ulong sourceLink = 1ul; + const ulong targetLink = 2ul; + const ulong connectionLink = 3ul; + + var memory = new HeapResizableDirectMemory(); + var constants = new LinksConstants(enableExternalReferencesSupport: true); + + unsafe + { + var header = memory.AllocateOrReserve(sizeof(LinksHeader)); + var links = memory.AllocateOrReserve(sizeof(RawLink) * 3); + + var bitStringMethods = new LinksSourcesBitStringIndexMethods(constants, (byte*)links, (byte*)header); + + // Set up a connection: both source and target should reference the same connection + bitStringMethods.SetBit(sourceLink, int.CreateTruncating(connectionLink) - 1, true); + bitStringMethods.SetBit(targetLink, int.CreateTruncating(connectionLink) - 1, true); + + // Test Search - should find the connection + var searchResult = bitStringMethods.Search(sourceLink, targetLink); + Assert.Equal(connectionLink, searchResult); + + // Test Search with no common connection + const ulong otherLink = 4ul; + bitStringMethods.SetBit(otherLink, 10, true); // Different bit position + var noConnectionResult = bitStringMethods.Search(sourceLink, otherLink); + Assert.Equal(0ul, noConnectionResult); + + memory.Free(); + } + } + + [Fact] + public static void BitStringEachUsageTest() + { + const ulong rootLink = 1ul; + const ulong childLink1 = 2ul; + const ulong childLink2 = 3ul; + + var memory = new HeapResizableDirectMemory(); + var constants = new LinksConstants(enableExternalReferencesSupport: true); + + unsafe + { + var header = memory.AllocateOrReserve(sizeof(LinksHeader)); + var links = memory.AllocateOrReserve(sizeof(RawLink) * 3); + + var bitStringMethods = new LinksTargetsBitStringIndexMethods(constants, (byte*)links, (byte*)header); + + // Attach children + var root = rootLink; + bitStringMethods.Attach(ref root, childLink1); + bitStringMethods.Attach(ref root, childLink2); + + // Test EachUsage + var visitedLinks = new System.Collections.Generic.List(); + bitStringMethods.EachUsage(rootLink, link => + { + visitedLinks.Add(link[0]); + return constants.Continue; + }); + + Assert.Equal(2, visitedLinks.Count); + Assert.Contains(childLink1, visitedLinks); + Assert.Contains(childLink2, visitedLinks); + + // Test EachUsage with early break + var limitedVisits = new System.Collections.Generic.List(); + bitStringMethods.EachUsage(rootLink, link => + { + limitedVisits.Add(link[0]); + return constants.Break; // Break after first visit + }); + + Assert.Equal(1, limitedVisits.Count); + + memory.Free(); + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Memory/IBitStringTreeMethods.cs b/csharp/Platform.Data.Doublets/Memory/IBitStringTreeMethods.cs new file mode 100644 index 000000000..0b28070d9 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Memory/IBitStringTreeMethods.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Platform.Delegates; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Memory +{ + /// + /// + /// Defines the bitstring tree methods interface. + /// + /// + /// + public interface IBitStringTreeMethods : ILinksTreeMethods + { + /// + /// + /// Gets the bitstring for the specified key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bitstring as BitArray. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + BitArray GetBitString(TLinkAddress key); + + /// + /// + /// Sets the bit at the specified position in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bit position. + /// + /// + /// + /// The bit value. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void SetBit(TLinkAddress key, int position, bool value); + + /// + /// + /// Gets the bit at the specified position in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bit position. + /// + /// + /// + /// The bit value. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + bool GetBit(TLinkAddress key, int position); + + /// + /// + /// Performs a bitwise AND operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + BitArray BitwiseAnd(TLinkAddress key1, TLinkAddress key2); + + /// + /// + /// Performs a bitwise OR operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + BitArray BitwiseOr(TLinkAddress key1, TLinkAddress key2); + + /// + /// + /// Performs a bitwise XOR operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + BitArray BitwiseXor(TLinkAddress key1, TLinkAddress key2); + + /// + /// + /// Counts the number of set bits in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The number of set bits. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + int CountSetBits(TLinkAddress key); + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Memory/IndexTreeType.cs b/csharp/Platform.Data.Doublets/Memory/IndexTreeType.cs index 328775912..7fe0f3eb5 100644 --- a/csharp/Platform.Data.Doublets/Memory/IndexTreeType.cs +++ b/csharp/Platform.Data.Doublets/Memory/IndexTreeType.cs @@ -37,6 +37,13 @@ public enum IndexTreeType /// /// /// - SizedAndThreadedAVLBalancedTree = 3 + SizedAndThreadedAVLBalancedTree = 3, + /// + /// + /// The bitstring index tree type. + /// + /// + /// + BitStringIndex = 4 } } diff --git a/csharp/Platform.Data.Doublets/Memory/Split/Generic/BitStringIndexMethodsBase.cs b/csharp/Platform.Data.Doublets/Memory/Split/Generic/BitStringIndexMethodsBase.cs new file mode 100644 index 000000000..25158d954 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Memory/Split/Generic/BitStringIndexMethodsBase.cs @@ -0,0 +1,511 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using Platform.Delegates; +using static System.Runtime.CompilerServices.Unsafe; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Memory.Split.Generic +{ + /// + /// + /// Represents the bitstring index methods base. + /// + /// + /// + /// + public abstract unsafe class BitStringIndexMethodsBase : IBitStringTreeMethods + where TLinkAddress : IUnsignedNumber, IComparisonOperators + { + /// + /// + /// The break. + /// + /// + /// + protected readonly TLinkAddress Break; + + /// + /// + /// The continue. + /// + /// + /// + protected readonly TLinkAddress Continue; + + /// + /// + /// The header. + /// + /// + /// + protected readonly byte* Header; + + /// + /// + /// The links data parts. + /// + /// + /// + protected readonly byte* LinksDataParts; + + /// + /// + /// The links index parts. + /// + /// + /// + protected readonly byte* LinksIndexParts; + + /// + /// + /// The bitstring storage dictionary. + /// + /// + /// + protected readonly Dictionary BitStringStorage; + + /// + /// + /// The default bitstring size. + /// + /// + /// + protected readonly int DefaultBitStringSize; + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A constants. + /// + /// + /// + /// A links data parts. + /// + /// + /// + /// A links index parts. + /// + /// + /// + /// A header. + /// + /// + /// + /// The default bitstring size. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected BitStringIndexMethodsBase(LinksConstants constants, byte* linksDataParts, byte* linksIndexParts, byte* header, int defaultBitStringSize = 1024) + { + LinksDataParts = linksDataParts; + LinksIndexParts = linksIndexParts; + Header = header; + Break = constants.Break; + Continue = constants.Continue; + BitStringStorage = new Dictionary(); + DefaultBitStringSize = defaultBitStringSize; + } + + /// + /// + /// Counts the usages using the specified root. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The link + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual TLinkAddress CountUsages(TLinkAddress root) + { + if (!BitStringStorage.TryGetValue(root, out var bitString)) + { + return TLinkAddress.Zero; + } + + var count = 0; + for (var i = 0; i < bitString.Length; i++) + { + if (bitString[i]) + { + count++; + } + } + return TLinkAddress.CreateTruncating(count); + } + + /// + /// + /// Searches for a link with the specified source and target. + /// + /// + /// + /// + /// The source. + /// + /// + /// + /// The target. + /// + /// + /// + /// The link + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual TLinkAddress Search(TLinkAddress source, TLinkAddress target) + { + if (!BitStringStorage.TryGetValue(source, out var sourceBits) || + !BitStringStorage.TryGetValue(target, out var targetBits)) + { + return TLinkAddress.Zero; + } + + var intersection = new BitArray(sourceBits).And(targetBits); + + for (var i = 0; i < intersection.Length; i++) + { + if (intersection[i]) + { + return TLinkAddress.CreateTruncating(i + 1); + } + } + + return TLinkAddress.Zero; + } + + /// + /// + /// Executes the handler for each usage. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The handler. + /// + /// + /// + /// The link + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual TLinkAddress EachUsage(TLinkAddress root, ReadHandler? handler) + { + if (!BitStringStorage.TryGetValue(root, out var bitString) || handler == null) + { + return Continue; + } + + for (var i = 0; i < bitString.Length; i++) + { + if (bitString[i]) + { + var link = TLinkAddress.CreateTruncating(i + 1); + var result = handler(new TLinkAddress[] { link, root, root }); + if (result == Break) + { + return Break; + } + } + } + + return Continue; + } + + /// + /// + /// Detaches the root. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The link index. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Detach(ref TLinkAddress root, TLinkAddress linkIndex) + { + if (!BitStringStorage.TryGetValue(root, out var bitString)) + { + return; + } + + var position = int.CreateTruncating(linkIndex) - 1; + if (position >= 0 && position < bitString.Length) + { + bitString[position] = false; + } + } + + /// + /// + /// Attaches the root. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The link index. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Attach(ref TLinkAddress root, TLinkAddress linkIndex) + { + if (!BitStringStorage.TryGetValue(root, out var bitString)) + { + bitString = new BitArray(DefaultBitStringSize); + BitStringStorage[root] = bitString; + } + + var position = int.CreateTruncating(linkIndex) - 1; + if (position >= bitString.Length) + { + var newSize = Math.Max(position + 1, bitString.Length * 2); + var newBitString = new BitArray(newSize); + + for (var i = 0; i < bitString.Length; i++) + { + newBitString[i] = bitString[i]; + } + + bitString = newBitString; + BitStringStorage[root] = bitString; + } + + if (position >= 0) + { + bitString[position] = true; + } + } + + /// + /// + /// Gets the bitstring for the specified key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bitstring as BitArray. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual BitArray GetBitString(TLinkAddress key) + { + if (BitStringStorage.TryGetValue(key, out var bitString)) + { + return new BitArray(bitString); + } + return new BitArray(DefaultBitStringSize); + } + + /// + /// + /// Sets the bit at the specified position in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bit position. + /// + /// + /// + /// The bit value. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void SetBit(TLinkAddress key, int position, bool value) + { + if (!BitStringStorage.TryGetValue(key, out var bitString)) + { + bitString = new BitArray(Math.Max(position + 1, DefaultBitStringSize)); + BitStringStorage[key] = bitString; + } + else if (position >= bitString.Length) + { + var newSize = Math.Max(position + 1, bitString.Length * 2); + var newBitString = new BitArray(newSize); + + for (var i = 0; i < bitString.Length; i++) + { + newBitString[i] = bitString[i]; + } + + bitString = newBitString; + BitStringStorage[key] = bitString; + } + + bitString[position] = value; + } + + /// + /// + /// Gets the bit at the specified position in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bit position. + /// + /// + /// + /// The bit value. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual bool GetBit(TLinkAddress key, int position) + { + if (!BitStringStorage.TryGetValue(key, out var bitString) || position >= bitString.Length || position < 0) + { + return false; + } + return bitString[position]; + } + + /// + /// + /// Performs a bitwise AND operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual BitArray BitwiseAnd(TLinkAddress key1, TLinkAddress key2) + { + var bits1 = GetBitString(key1); + var bits2 = GetBitString(key2); + return bits1.And(bits2); + } + + /// + /// + /// Performs a bitwise OR operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual BitArray BitwiseOr(TLinkAddress key1, TLinkAddress key2) + { + var bits1 = GetBitString(key1); + var bits2 = GetBitString(key2); + return bits1.Or(bits2); + } + + /// + /// + /// Performs a bitwise XOR operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual BitArray BitwiseXor(TLinkAddress key1, TLinkAddress key2) + { + var bits1 = GetBitString(key1); + var bits2 = GetBitString(key2); + return bits1.Xor(bits2); + } + + /// + /// + /// Counts the number of set bits in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The number of set bits. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual int CountSetBits(TLinkAddress key) + { + if (!BitStringStorage.TryGetValue(key, out var bitString)) + { + return 0; + } + + var count = 0; + for (var i = 0; i < bitString.Length; i++) + { + if (bitString[i]) + { + count++; + } + } + return count; + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Memory/Split/Generic/ExternalLinksBitStringIndexMethods.cs b/csharp/Platform.Data.Doublets/Memory/Split/Generic/ExternalLinksBitStringIndexMethods.cs new file mode 100644 index 000000000..c6d02e2d1 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Memory/Split/Generic/ExternalLinksBitStringIndexMethods.cs @@ -0,0 +1,83 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using static System.Runtime.CompilerServices.Unsafe; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Memory.Split.Generic +{ + /// + /// + /// Represents the external links bitstring index methods. + /// + /// + /// + /// + public unsafe class ExternalLinksBitStringIndexMethods : BitStringIndexMethodsBase + where TLinkAddress : IUnsignedNumber, IComparisonOperators + { + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A constants. + /// + /// + /// + /// A links data parts. + /// + /// + /// + /// A links index parts. + /// + /// + /// + /// A header. + /// + /// + /// + /// The default bitstring size. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ExternalLinksBitStringIndexMethods(LinksConstants constants, byte* linksDataParts, byte* linksIndexParts, byte* header, int defaultBitStringSize = 1024) + : base(constants, linksDataParts, linksIndexParts, header, defaultBitStringSize) + { + } + + /// + /// + /// Gets the link data at the specified index. + /// + /// + /// + /// + /// The link index. + /// + /// + /// + /// The data index. + /// + /// + /// + /// The link data. + /// + /// + public TLinkAddress this[TLinkAddress link, TLinkAddress index] + { + get + { + var linkIndexPartPointer = (TLinkAddress*)(LinksIndexParts + (long)(ulong.CreateTruncating(link) * (ulong)sizeof(RawLinkIndexPart))); + return Add(ref AsRef(linkIndexPartPointer), int.CreateTruncating(index)); + } + set + { + var linkIndexPartPointer = (TLinkAddress*)(LinksIndexParts + (long)(ulong.CreateTruncating(link) * (ulong)sizeof(RawLinkIndexPart))); + Add(ref AsRef(linkIndexPartPointer), int.CreateTruncating(index)) = value; + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Memory/Split/Generic/InternalLinksBitStringIndexMethods.cs b/csharp/Platform.Data.Doublets/Memory/Split/Generic/InternalLinksBitStringIndexMethods.cs new file mode 100644 index 000000000..e3c818914 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Memory/Split/Generic/InternalLinksBitStringIndexMethods.cs @@ -0,0 +1,83 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using static System.Runtime.CompilerServices.Unsafe; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Memory.Split.Generic +{ + /// + /// + /// Represents the internal links bitstring index methods. + /// + /// + /// + /// + public unsafe class InternalLinksBitStringIndexMethods : BitStringIndexMethodsBase + where TLinkAddress : IUnsignedNumber, IComparisonOperators + { + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A constants. + /// + /// + /// + /// A links data parts. + /// + /// + /// + /// A links index parts. + /// + /// + /// + /// A header. + /// + /// + /// + /// The default bitstring size. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public InternalLinksBitStringIndexMethods(LinksConstants constants, byte* linksDataParts, byte* linksIndexParts, byte* header, int defaultBitStringSize = 1024) + : base(constants, linksDataParts, linksIndexParts, header, defaultBitStringSize) + { + } + + /// + /// + /// Gets the link data at the specified index. + /// + /// + /// + /// + /// The link index. + /// + /// + /// + /// The data index. + /// + /// + /// + /// The link data. + /// + /// + public TLinkAddress this[TLinkAddress link, TLinkAddress index] + { + get + { + var linkDataPartPointer = (TLinkAddress*)(LinksDataParts + (long)(ulong.CreateTruncating(link) * (ulong)sizeof(RawLinkDataPart))); + return Add(ref AsRef(linkDataPartPointer), int.CreateTruncating(index)); + } + set + { + var linkDataPartPointer = (TLinkAddress*)(LinksDataParts + (long)(ulong.CreateTruncating(link) * (ulong)sizeof(RawLinkDataPart))); + Add(ref AsRef(linkDataPartPointer), int.CreateTruncating(index)) = value; + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksBitStringIndexMethodsBase.cs b/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksBitStringIndexMethodsBase.cs new file mode 100644 index 000000000..878c72e43 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksBitStringIndexMethodsBase.cs @@ -0,0 +1,530 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using Platform.Delegates; +using static System.Runtime.CompilerServices.Unsafe; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Memory.United.Generic +{ + /// + /// + /// Represents the links bitstring index methods base. + /// + /// + /// + /// + public abstract unsafe class LinksBitStringIndexMethodsBase : IBitStringTreeMethods + where TLinkAddress : IUnsignedNumber, IComparisonOperators + { + /// + /// + /// The break. + /// + /// + /// + protected readonly TLinkAddress Break; + + /// + /// + /// The continue. + /// + /// + /// + protected readonly TLinkAddress Continue; + + /// + /// + /// The header. + /// + /// + /// + protected readonly byte* Header; + + /// + /// + /// The links. + /// + /// + /// + protected readonly byte* Links; + + /// + /// + /// The bitstring storage dictionary. + /// + /// + /// + protected readonly Dictionary BitStringStorage; + + /// + /// + /// The default bitstring size. + /// + /// + /// + protected readonly int DefaultBitStringSize; + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A constants. + /// + /// + /// + /// A links. + /// + /// + /// + /// A header. + /// + /// + /// + /// The default bitstring size. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected LinksBitStringIndexMethodsBase(LinksConstants constants, byte* links, byte* header, int defaultBitStringSize = 1024) + { + Links = links; + Header = header; + Break = constants.Break; + Continue = constants.Continue; + BitStringStorage = new Dictionary(); + DefaultBitStringSize = defaultBitStringSize; + } + + /// + /// + /// Gets the link data at the specified index. + /// + /// + /// + /// + /// The link index. + /// + /// + /// + /// The data index. + /// + /// + /// + /// The link data. + /// + /// + public TLinkAddress this[TLinkAddress link, TLinkAddress index] + { + get + { + var linkPointer = (TLinkAddress*)(Links + (long)(ulong.CreateTruncating(link) * (ulong)sizeof(RawLink))); + return Add(ref AsRef(linkPointer), int.CreateTruncating(index)); + } + set + { + var linkPointer = (TLinkAddress*)(Links + (long)(ulong.CreateTruncating(link) * (ulong)sizeof(RawLink))); + Add(ref AsRef(linkPointer), int.CreateTruncating(index)) = value; + } + } + + /// + /// + /// Counts the usages using the specified root. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The link + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual TLinkAddress CountUsages(TLinkAddress root) + { + if (!BitStringStorage.TryGetValue(root, out var bitString)) + { + return TLinkAddress.Zero; + } + + var count = 0; + for (var i = 0; i < bitString.Length; i++) + { + if (bitString[i]) + { + count++; + } + } + return TLinkAddress.CreateTruncating(count); + } + + /// + /// + /// Searches for a link with the specified source and target. + /// + /// + /// + /// + /// The source. + /// + /// + /// + /// The target. + /// + /// + /// + /// The link + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual TLinkAddress Search(TLinkAddress source, TLinkAddress target) + { + if (!BitStringStorage.TryGetValue(source, out var sourceBits) || + !BitStringStorage.TryGetValue(target, out var targetBits)) + { + return TLinkAddress.Zero; + } + + var intersection = new BitArray(sourceBits).And(targetBits); + + for (var i = 0; i < intersection.Length; i++) + { + if (intersection[i]) + { + return TLinkAddress.CreateTruncating(i + 1); + } + } + + return TLinkAddress.Zero; + } + + /// + /// + /// Executes the handler for each usage. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The handler. + /// + /// + /// + /// The link + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual TLinkAddress EachUsage(TLinkAddress root, ReadHandler? handler) + { + if (!BitStringStorage.TryGetValue(root, out var bitString) || handler == null) + { + return Continue; + } + + for (var i = 0; i < bitString.Length; i++) + { + if (bitString[i]) + { + var link = TLinkAddress.CreateTruncating(i + 1); + var result = handler(new TLinkAddress[] { link, root, root }); + if (result == Break) + { + return Break; + } + } + } + + return Continue; + } + + /// + /// + /// Detaches the root. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The link index. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Detach(ref TLinkAddress root, TLinkAddress linkIndex) + { + if (!BitStringStorage.TryGetValue(root, out var bitString)) + { + return; + } + + var position = int.CreateTruncating(linkIndex) - 1; + if (position >= 0 && position < bitString.Length) + { + bitString[position] = false; + } + } + + /// + /// + /// Attaches the root. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The link index. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Attach(ref TLinkAddress root, TLinkAddress linkIndex) + { + if (!BitStringStorage.TryGetValue(root, out var bitString)) + { + bitString = new BitArray(DefaultBitStringSize); + BitStringStorage[root] = bitString; + } + + var position = int.CreateTruncating(linkIndex) - 1; + if (position >= bitString.Length) + { + var newSize = Math.Max(position + 1, bitString.Length * 2); + var newBitString = new BitArray(newSize); + + for (var i = 0; i < bitString.Length; i++) + { + newBitString[i] = bitString[i]; + } + + bitString = newBitString; + BitStringStorage[root] = bitString; + } + + if (position >= 0) + { + bitString[position] = true; + } + } + + /// + /// + /// Gets the bitstring for the specified key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bitstring as BitArray. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual BitArray GetBitString(TLinkAddress key) + { + if (BitStringStorage.TryGetValue(key, out var bitString)) + { + return new BitArray(bitString); + } + return new BitArray(DefaultBitStringSize); + } + + /// + /// + /// Sets the bit at the specified position in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bit position. + /// + /// + /// + /// The bit value. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void SetBit(TLinkAddress key, int position, bool value) + { + if (!BitStringStorage.TryGetValue(key, out var bitString)) + { + bitString = new BitArray(Math.Max(position + 1, DefaultBitStringSize)); + BitStringStorage[key] = bitString; + } + else if (position >= bitString.Length) + { + var newSize = Math.Max(position + 1, bitString.Length * 2); + var newBitString = new BitArray(newSize); + + for (var i = 0; i < bitString.Length; i++) + { + newBitString[i] = bitString[i]; + } + + bitString = newBitString; + BitStringStorage[key] = bitString; + } + + bitString[position] = value; + } + + /// + /// + /// Gets the bit at the specified position in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The bit position. + /// + /// + /// + /// The bit value. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual bool GetBit(TLinkAddress key, int position) + { + if (!BitStringStorage.TryGetValue(key, out var bitString) || position >= bitString.Length || position < 0) + { + return false; + } + return bitString[position]; + } + + /// + /// + /// Performs a bitwise AND operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual BitArray BitwiseAnd(TLinkAddress key1, TLinkAddress key2) + { + var bits1 = GetBitString(key1); + var bits2 = GetBitString(key2); + return bits1.And(bits2); + } + + /// + /// + /// Performs a bitwise OR operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual BitArray BitwiseOr(TLinkAddress key1, TLinkAddress key2) + { + var bits1 = GetBitString(key1); + var bits2 = GetBitString(key2); + return bits1.Or(bits2); + } + + /// + /// + /// Performs a bitwise XOR operation between two bitstrings. + /// + /// + /// + /// + /// The first key. + /// + /// + /// + /// The second key. + /// + /// + /// + /// The result bitstring. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual BitArray BitwiseXor(TLinkAddress key1, TLinkAddress key2) + { + var bits1 = GetBitString(key1); + var bits2 = GetBitString(key2); + return bits1.Xor(bits2); + } + + /// + /// + /// Counts the number of set bits in the bitstring for the given key. + /// + /// + /// + /// + /// The key. + /// + /// + /// + /// The number of set bits. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual int CountSetBits(TLinkAddress key) + { + if (!BitStringStorage.TryGetValue(key, out var bitString)) + { + return 0; + } + + var count = 0; + for (var i = 0; i < bitString.Length; i++) + { + if (bitString[i]) + { + count++; + } + } + return count; + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksSourcesBitStringIndexMethods.cs b/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksSourcesBitStringIndexMethods.cs new file mode 100644 index 000000000..3270f0f40 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksSourcesBitStringIndexMethods.cs @@ -0,0 +1,46 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Memory.United.Generic +{ + /// + /// + /// Represents the links sources bitstring index methods. + /// + /// + /// + /// + public unsafe class LinksSourcesBitStringIndexMethods : LinksBitStringIndexMethodsBase + where TLinkAddress : IUnsignedNumber, IComparisonOperators + { + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A constants. + /// + /// + /// + /// A links. + /// + /// + /// + /// A header. + /// + /// + /// + /// The default bitstring size. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinksSourcesBitStringIndexMethods(LinksConstants constants, byte* links, byte* header, int defaultBitStringSize = 1024) + : base(constants, links, header, defaultBitStringSize) + { + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksTargetsBitStringIndexMethods.cs b/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksTargetsBitStringIndexMethods.cs new file mode 100644 index 000000000..6ffd6353d --- /dev/null +++ b/csharp/Platform.Data.Doublets/Memory/United/Generic/LinksTargetsBitStringIndexMethods.cs @@ -0,0 +1,46 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Memory.United.Generic +{ + /// + /// + /// Represents the links targets bitstring index methods. + /// + /// + /// + /// + public unsafe class LinksTargetsBitStringIndexMethods : LinksBitStringIndexMethodsBase + where TLinkAddress : IUnsignedNumber, IComparisonOperators + { + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// A constants. + /// + /// + /// + /// A links. + /// + /// + /// + /// A header. + /// + /// + /// + /// The default bitstring size. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinksTargetsBitStringIndexMethods(LinksConstants constants, byte* links, byte* header, int defaultBitStringSize = 1024) + : base(constants, links, header, defaultBitStringSize) + { + } + } +} \ No newline at end of file diff --git a/examples/BitStringIndexingExample.cs b/examples/BitStringIndexingExample.cs new file mode 100644 index 000000000..97dc23012 --- /dev/null +++ b/examples/BitStringIndexingExample.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections; +using Platform.Data.Doublets; +using Platform.Data.Doublets.Memory; +using Platform.Data.Doublets.Memory.United.Generic; +using Platform.Memory; + +namespace Platform.Data.Doublets.Examples +{ + /// + /// Example demonstrating bitstring indexing functionality. + /// + public static class BitStringIndexingExample + { + /// + /// Demonstrates basic bitstring index operations. + /// + public static void BasicBitStringIndexOperations() + { + Console.WriteLine("=== BitString Index Basic Operations ==="); + + var memory = new HeapResizableDirectMemory(); + var constants = new LinksConstants(enableExternalReferencesSupport: true); + + unsafe + { + var header = memory.AllocateOrReserve(sizeof(LinksHeader)); + var links = memory.AllocateOrReserve(sizeof(RawLink) * 10); + + var bitStringMethods = new LinksSourcesBitStringIndexMethods(constants, (byte*)links, (byte*)header); + + // Set up some data relationships + const ulong sourceLink = 1ul; + const ulong targetLink = 2ul; + const ulong relationshipLink = 3ul; + + Console.WriteLine("Setting up bitstring relationships..."); + + // Mark that sourceLink has relationship at position 2 (relationshipLink - 1) + bitStringMethods.SetBit(sourceLink, (int)relationshipLink - 1, true); + bitStringMethods.SetBit(sourceLink, 5, true); + bitStringMethods.SetBit(sourceLink, 10, true); + + // Mark that targetLink has relationship at position 2 (relationshipLink - 1) + bitStringMethods.SetBit(targetLink, (int)relationshipLink - 1, true); + bitStringMethods.SetBit(targetLink, 7, true); + + Console.WriteLine($"Source link has {bitStringMethods.CountSetBits(sourceLink)} relationships"); + Console.WriteLine($"Target link has {bitStringMethods.CountSetBits(targetLink)} relationships"); + + // Demonstrate bitwise operations + Console.WriteLine("\nPerforming bitwise operations..."); + + var andResult = bitStringMethods.BitwiseAnd(sourceLink, targetLink); + Console.WriteLine("Bitwise AND result (common relationships):"); + for (int i = 0; i < Math.Min(15, andResult.Length); i++) + { + if (andResult[i]) + { + Console.WriteLine($" Position {i}: true (relationship {i + 1})"); + } + } + + var orResult = bitStringMethods.BitwiseOr(sourceLink, targetLink); + Console.WriteLine("Bitwise OR result (all relationships):"); + for (int i = 0; i < Math.Min(15, orResult.Length); i++) + { + if (orResult[i]) + { + Console.WriteLine($" Position {i}: true (relationship {i + 1})"); + } + } + + // Search for common relationships + var searchResult = bitStringMethods.Search(sourceLink, targetLink); + Console.WriteLine($"\nSearch result: {searchResult} (first common relationship)"); + + memory.Free(); + } + + Console.WriteLine("=== Example Complete ===\n"); + } + + /// + /// Demonstrates attach/detach operations. + /// + public static void AttachDetachOperations() + { + Console.WriteLine("=== BitString Attach/Detach Operations ==="); + + var memory = new HeapResizableDirectMemory(); + var constants = new LinksConstants(enableExternalReferencesSupport: true); + + unsafe + { + var header = memory.AllocateOrReserve(sizeof(LinksHeader)); + var links = memory.AllocateOrReserve(sizeof(RawLink) * 10); + + var bitStringMethods = new LinksTargetsBitStringIndexMethods(constants, (byte*)links, (byte*)header); + + const ulong rootLink = 1ul; + const ulong childLink1 = 2ul; + const ulong childLink2 = 3ul; + const ulong childLink3 = 4ul; + + Console.WriteLine("Attaching child links to root..."); + + // Attach children to root + var root = rootLink; + bitStringMethods.Attach(ref root, childLink1); + bitStringMethods.Attach(ref root, childLink2); + bitStringMethods.Attach(ref root, childLink3); + + Console.WriteLine($"Root link {rootLink} now has {bitStringMethods.CountUsages(rootLink)} usages"); + + // List all usages + Console.WriteLine("Enumerating all usages:"); + bitStringMethods.EachUsage(rootLink, link => + { + Console.WriteLine($" Usage: {link[0]}"); + return constants.Continue; + }); + + // Detach one child + Console.WriteLine($"\nDetaching child link {childLink2}..."); + bitStringMethods.Detach(ref root, childLink2); + + Console.WriteLine($"Root link {rootLink} now has {bitStringMethods.CountUsages(rootLink)} usages"); + + Console.WriteLine("Remaining usages:"); + bitStringMethods.EachUsage(rootLink, link => + { + Console.WriteLine($" Usage: {link[0]}"); + return constants.Continue; + }); + + memory.Free(); + } + + Console.WriteLine("=== Example Complete ===\n"); + } + + /// + /// Runs all examples. + /// + public static void RunAll() + { + Console.WriteLine("BitString Indexing Examples\n"); + + BasicBitStringIndexOperations(); + AttachDetachOperations(); + + Console.WriteLine("All examples completed successfully!"); + } + } +} \ No newline at end of file