< Summary

Information
Class: SwiftCollections.SwiftHashTools
Assembly: SwiftCollections
File(s): /home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Utility/SwiftHashTools.cs
Line coverage
100%
Covered lines: 144
Uncovered lines: 0
Coverable lines: 144
Total lines: 331
Line coverage: 100%
Branch coverage
98%
Covered branches: 55
Total branches: 56
Branch coverage: 98.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Utility/SwiftHashTools.cs

#LineLine coverage
 1using System;
 2using System.Collections;
 3using System.Collections.Generic;
 4using System.Runtime.CompilerServices;
 5using System.Runtime.Serialization;
 6using System.Security.Cryptography;
 7using System.Threading;
 8
 9namespace SwiftCollections;
 10
 11/// <summary>
 12/// Provides utility functions for generating, combining, and working with hash codes.
 13/// </summary>
 14public static class SwiftHashTools
 15{
 16    internal const int DefaultDeterministicStringHashSeed = 0x51C4B5D;
 17    internal const int DefaultDeterministicObjectHashSeed = 0x1F6E4D75;
 18
 119    private static readonly IEqualityComparer<string> s_deterministicStringComparer =
 120        new SwiftDeterministicStringEqualityComparer(DefaultDeterministicStringHashSeed);
 21
 122    private static readonly IEqualityComparer<object> s_deterministicObjectComparer =
 123        new SwiftDeterministicObjectEqualityComparer(DefaultDeterministicObjectHashSeed);
 24
 25    /// <summary>
 26    /// Provides a cryptographic random number generator for collision-hardening entropy.
 27    /// </summary>
 28    private static RandomNumberGenerator rng;
 29
 30    /// <summary>
 31    /// Stores random bytes used for entropy generation.
 32    /// </summary>
 33    private static byte[] data;
 34
 35    /// <summary>
 36    /// Tracks the current index in the entropy data buffer.
 37    /// </summary>
 138    private static int currentIndex = 1024;
 39
 40    /// <summary>
 41    /// Synchronization object for thread-safe operations.
 42    /// </summary>
 143    private static readonly object lockObj = new();
 44
 45    /// <summary>
 46    /// Holds serialization information for objects during the serialization process.
 47    /// </summary>
 48    private static ConditionalWeakTable<object, SerializationInfo> s_SerializationInfoTable;
 49
 50    /// <summary>
 51    /// Gets the table that stores serialization information for objects.
 52    /// </summary>
 53    internal static ConditionalWeakTable<object, SerializationInfo> SerializationInfoTable
 54    {
 55        get
 256        {
 257            if (s_SerializationInfoTable == null)
 158            {
 159                ConditionalWeakTable<object, SerializationInfo> value = new ConditionalWeakTable<object, SerializationIn
 160                Interlocked.CompareExchange(ref s_SerializationInfoTable, value, null);
 161            }
 62
 263            return s_SerializationInfoTable;
 264        }
 65    }
 66
 67    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 468    public static bool IsPowerOfTwo(int x) => (x != 0) && ((x & (x - 1)) == 0);
 69
 70    /// <summary>
 71    /// Calculates the smallest power of two that is greater than or equal to the specified integer.
 72    /// This method is used to ensure that capacities are powers of two, enabling optimizations
 73    /// in indexing operations through bitwise arithmetic.
 74    /// </summary>
 75    /// <param name="value">The integer value for which to find the next power of two.</param>
 76    /// <returns>The smallest power of two greater than or equal to <paramref name="value"/>.</returns>
 77    /// <remarks>
 78    /// If <paramref name="value"/> is less than or equal to zero, the method returns 1.
 79    /// If <paramref name="value"/> is too large to represent as a power of two within an <c>int</c>,
 80    /// the methods returns `int.Max`.
 81    /// </remarks>
 82    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 83    public static int NextPowerOfTwo(int value)
 1161484    {
 1161685        if (value <= 0) return 1;
 1161386        if (value >= (1 << 30)) return int.MaxValue;
 87
 1161188        value--;
 1161189        value |= value >> 1;
 1161190        value |= value >> 2;
 1161191        value |= value >> 4;
 1161192        value |= value >> 8;
 1161193        value |= value >> 16;
 1161194        value++;
 1161195        return value;
 1161496    }
 97
 98    /// <summary>
 99    /// Combines the hash codes of the elements in a tuple using a DJB2-inspired mixing strategy.
 100    /// </summary>
 101    public static int CombineHashCodes(
 102        this ITuple tupled,
 103        int seed = 5381,
 104        int shift1 = 16,
 105        int shift2 = 5,
 106        int shift3 = 27,
 107        int factor3 = 1566083941)
 1108    {
 1109        int hash1 = (seed << shift1) + seed;
 1110        int hash2 = hash1;
 111
 10112        for (int i = 0; i < tupled.Length; i++)
 4113        {
 4114            int itemHash = tupled[i]?.GetHashCode() ?? 0;
 115            unchecked
 4116            {
 4117                if (i % 2 == 0)
 2118                    hash1 = ((hash1 << shift2) + hash1 + (hash1 >> shift3)) ^ itemHash;
 119                else
 2120                    hash2 = ((hash2 << shift2) + hash2 + (hash2 >> shift3)) ^ itemHash;
 4121            }
 4122        }
 123
 1124        return hash1 ^ (hash2 * factor3);
 1125    }
 126
 127    /// <summary>
 128    /// Combines the hash codes of a set of objects using a DJB2-inspired mixing strategy.
 129    /// </summary>
 130    public static int CombineHashCodes(
 131        this object[] values,
 132        int seed = 5381,
 133        int shift1 = 16,
 134        int shift2 = 5,
 135        int shift3 = 27,
 136        int factor3 = 1566083941)
 3137    {
 3138        int hash1 = (seed << shift1) + seed;
 3139        int hash2 = hash1;
 140
 30141        for (int i = 0; i < values.Length; i++)
 12142        {
 12143            var itemHash = values[i]?.GetHashCode() ?? 0;
 144            unchecked
 12145            {
 12146                if ((i & 1) == 0)
 6147                    hash1 = ((hash1 << shift2) + hash1 + (hash1 >> shift3)) ^ itemHash;
 148                else
 6149                    hash2 = ((hash2 << shift2) + hash2 + (hash2 >> shift3)) ^ itemHash;
 12150            }
 12151        }
 152
 3153        return hash1 + (hash2 * factor3);
 3154    }
 155
 156    /// <summary>
 157    /// Combines the hash codes of the provided objects using a DJB2-inspired mixing strategy.
 158    /// </summary>
 159    public static int CombineHashCodes(
 160        params object[] values)
 2161    {
 2162        return values.CombineHashCodes();
 2163    }
 164
 165    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 166    public static bool IsWellKnownEqualityComparer(object comparer)
 8167    {
 8168        return comparer == null
 8169            || comparer == EqualityComparer<string>.Default
 8170            || comparer == EqualityComparer<object>.Default
 8171            || comparer is SwiftDeterministicStringEqualityComparer
 8172            || comparer is SwiftDeterministicObjectEqualityComparer;
 8173    }
 174
 175    /// <summary>
 176    /// Gets the default comparer used by SwiftCollections for the specified type when no comparer is supplied.
 177    /// String keys are hashed deterministically. Object keys hash strings deterministically, while other object-key
 178    /// determinism still depends on the underlying key type's <see cref="object.GetHashCode()"/> implementation.
 179    /// </summary>
 180    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 181    public static IEqualityComparer<T> GetDeterministicEqualityComparer<T>()
 304182    {
 304183        if (typeof(T) == typeof(string))
 83184            return (IEqualityComparer<T>)GetDeterministicStringEqualityComparer();
 185
 221186        if (typeof(T) == typeof(object))
 6187            return (IEqualityComparer<T>)GetDeterministicObjectEqualityComparer();
 188
 215189        return EqualityComparer<T>.Default;
 304190    }
 191
 192    /// <summary>
 193    /// Gets the deterministic default comparer used by SwiftCollections for strings.
 194    /// </summary>
 195    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 196    public static IEqualityComparer<string> GetDeterministicStringEqualityComparer()
 84197        => s_deterministicStringComparer;
 198
 199    /// <summary>
 200    /// Gets a deterministic string comparer using the supplied seed.
 201    /// </summary>
 202    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 203    public static IEqualityComparer<string> GetDeterministicStringEqualityComparer(int seed)
 2204        => seed == DefaultDeterministicStringHashSeed
 2205            ? s_deterministicStringComparer
 2206            : new SwiftDeterministicStringEqualityComparer(seed);
 207
 208    /// <summary>
 209    /// Gets the default comparer used by SwiftCollections for object keys.
 210    /// Strings are hashed deterministically even when stored as objects. Other object keys still depend on their
 211    /// runtime <see cref="object.GetHashCode()"/> implementations.
 212    /// </summary>
 213    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 214    public static IEqualityComparer<object> GetDeterministicObjectEqualityComparer()
 7215        => s_deterministicObjectComparer;
 216
 217    /// <summary>
 218    /// Gets an object comparer using the supplied seed.
 219    /// Strings are hashed deterministically even when stored as objects. Other object keys still depend on their
 220    /// runtime <see cref="object.GetHashCode()"/> implementations.
 221    /// </summary>
 222    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 223    public static IEqualityComparer<object> GetDeterministicObjectEqualityComparer(int seed)
 2224        => seed == DefaultDeterministicObjectHashSeed
 2225            ? s_deterministicObjectComparer
 2226            : new SwiftDeterministicObjectEqualityComparer(seed);
 227
 228    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 229    internal static IEqualityComparer<T> GetDefaultEqualityComparer<T>(IEqualityComparer<T> comparer = null)
 360230        => comparer ?? GetDeterministicEqualityComparer<T>();
 231
 232    public static IEqualityComparer GetSwiftEqualityComparer(object comparer)
 7233    {
 7234        if (comparer == EqualityComparer<string>.Default || comparer is SwiftDeterministicStringEqualityComparer)
 4235            return new SwiftStringEqualityComparer();
 236
 3237        if (comparer == EqualityComparer<object>.Default || comparer is SwiftDeterministicObjectEqualityComparer)
 2238            return new SwiftObjectEqualityComparer();
 239
 1240        return new SwiftObjectEqualityComparer();
 7241    }
 242
 243    /// <summary>
 244    /// Generates a cryptographically strong random 64-bit integer for collision-hardening entropy.
 245    /// </summary>
 246    /// <returns>A 64-bit integer filled with cryptographically strong random bytes.</returns>
 247    internal static long GetEntropy()
 139248    {
 139249        lock (lockObj)
 139250        {
 139251            if (currentIndex == 1024)
 2252            {
 2253                if (rng == null)
 1254                {
 1255                    rng = RandomNumberGenerator.Create();
 1256                    data = new byte[1024];
 1257                }
 258
 2259                rng.GetBytes(data);
 2260                currentIndex = 0;
 2261            }
 262
 139263            long result = BitConverter.ToInt64(data, currentIndex);
 139264            currentIndex += 8;
 139265            return result;
 266        }
 139267    }
 268
 269    /// <summary>
 270    /// Computes a hash code for a string using the MurmurHash3 algorithm, incorporating entropy for randomization.
 271    /// </summary>
 272    /// <param name="key">The string to hash.</param>
 273    /// <param name="seed">The seed value for the hash function.</param>
 274    /// <returns>An integer hash code for the string.</returns>
 275    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 276    public static unsafe int MurmurHash3(string key, int seed)
 305308277    {
 278        const uint c1 = 0xcc9e2d51;
 279        const uint c2 = 0x1b873593;
 305308280        uint h1 = (uint)seed;
 281
 305308282        fixed (char* ptr = key)
 305308283        {
 305308284            int length = key.Length;
 305308285            int blockCount = length / 2;
 286
 287            unchecked
 305308288            {
 289                // Process 32-bit blocks
 7734454290                for (int i = 0; i < blockCount; i++)
 3561919291                {
 3561919292                    uint k1 = *(uint*)(ptr + (i * 2));
 3561919293                    k1 *= c1;
 3561919294                    k1 = RotateLeft(k1, 15);
 3561919295                    k1 *= c2;
 3561919296                    h1 ^= k1;
 3561919297                    h1 = RotateLeft(h1, 13);
 3561919298                    h1 = h1 * 5 + 0xe6546b64;
 3561919299                }
 300
 301                // Handle remaining characters if odd length
 305308302                if (length % 2 != 0)
 25793303                {
 25793304                    uint k1 = ptr[length - 1];
 25793305                    k1 *= c1;
 25793306                    k1 = RotateLeft(k1, 15);
 25793307                    k1 *= c2;
 25793308                    h1 ^= k1;
 25793309                }
 305308310            }
 311
 305308312            h1 ^= (uint)length;
 305308313            h1 = FMix(h1);
 305308314            return (int)(h1 & 0x7FFFFFFF);
 315        }
 305308316    }
 317
 318    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 7149631319    private static uint RotateLeft(uint x, int r) => (x << r) | (x >> (32 - r));
 320
 321    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 322    private static uint FMix(uint h)
 305308323    {
 305308324        h ^= h >> 16;
 305308325        h *= 0x85ebca6b;
 305308326        h ^= h >> 13;
 305308327        h *= 0xc2b2ae35;
 305308328        h ^= h >> 16;
 305308329        return h;
 305308330    }
 331}